@hardlydifficult/pr-analyzer 1.0.0 → 1.0.2

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 +284 -0
  2. package/package.json +1 -1
package/README.md ADDED
@@ -0,0 +1,284 @@
1
+ # @hardlydifficult/pr-analyzer
2
+
3
+ Analyzes and classifies GitHub pull requests by status, determining available actions and enabling custom logic via hooks.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @hardlydifficult/pr-analyzer
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ Analyze a single PR and determine its status:
14
+
15
+ ```typescript
16
+ import { scanSinglePR } from "@hardlydifficult/pr-analyzer";
17
+ import { createGitHubClient } from "@hardlydifficult/github";
18
+
19
+ const client = createGitHubClient(process.env.GITHUB_TOKEN);
20
+ const scanned = await scanSinglePR(
21
+ client,
22
+ "@cursor",
23
+ "owner",
24
+ "repo",
25
+ 42
26
+ );
27
+
28
+ console.log(scanned.status); // "ready_to_merge", "ci_running", "draft", etc.
29
+ console.log(scanned.ciStatus); // { isRunning, hasFailed, allPassed, summary }
30
+ ```
31
+
32
+ ## PR Analysis
33
+
34
+ Analyze a PR to fetch CI checks, reviews, comments, and determine its status.
35
+
36
+ ### `analyzePR()`
37
+
38
+ Analyzes a single PR and returns detailed status information:
39
+
40
+ ```typescript
41
+ import { analyzePR } from "@hardlydifficult/pr-analyzer";
42
+
43
+ const scanned = await analyzePR(
44
+ client,
45
+ "owner",
46
+ "repo",
47
+ prObject,
48
+ "@cursor"
49
+ );
50
+
51
+ // Returns ScannedPR with:
52
+ // - status: Core status like "ready_to_merge", "ci_running", "draft", etc.
53
+ // - ciStatus: { isRunning, hasFailed, allPassed, summary }
54
+ // - hasConflicts: boolean
55
+ // - waitingOnBot: boolean (true if bot was mentioned but hasn't replied)
56
+ // - daysSinceUpdate: number
57
+ // - pr: Original PullRequest object
58
+ // - repo: Repository object
59
+ ```
60
+
61
+ **Core statuses** (in priority order):
62
+ - `draft` — PR is in draft mode
63
+ - `ci_running` — CI checks are in progress
64
+ - `ci_failed` — One or more CI checks failed
65
+ - `has_conflicts` — PR has merge conflicts
66
+ - `waiting_on_bot` — Bot was mentioned but hasn't replied
67
+ - `changes_requested` — Reviewer requested changes
68
+ - `ready_to_merge` — All checks passed, no issues
69
+ - `approved` — PR is approved but CI not yet passed
70
+ - `needs_review` — Default status, awaiting review
71
+
72
+ ### `analyzeAll()`
73
+
74
+ Analyzes multiple discovered PRs in sequence, logging failures:
75
+
76
+ ```typescript
77
+ import { analyzeAll } from "@hardlydifficult/pr-analyzer";
78
+
79
+ const scanned = await analyzeAll(
80
+ discoveredPRs,
81
+ client,
82
+ "@cursor",
83
+ logger // optional Logger
84
+ );
85
+ ```
86
+
87
+ ### `scanSinglePR()`
88
+
89
+ Convenience function for real-time event handling — fetches and analyzes a PR by number:
90
+
91
+ ```typescript
92
+ import { scanSinglePR } from "@hardlydifficult/pr-analyzer";
93
+
94
+ const scanned = await scanSinglePR(
95
+ client,
96
+ "@cursor",
97
+ "owner",
98
+ "repo",
99
+ 42
100
+ );
101
+ ```
102
+
103
+ ## Custom Status Logic
104
+
105
+ Override status determination with custom logic via `AnalyzerHooks`:
106
+
107
+ ```typescript
108
+ import { analyzePR } from "@hardlydifficult/pr-analyzer";
109
+ import type { AnalyzerHooks } from "@hardlydifficult/pr-analyzer";
110
+
111
+ const hooks: AnalyzerHooks = {
112
+ resolveStatus: (coreStatus, details) => {
113
+ // Return custom status string to override, or undefined to keep core status
114
+ if (coreStatus === "ci_failed" && details.checks.some(c => c.name === "lint")) {
115
+ return "lint_failed";
116
+ }
117
+ return undefined;
118
+ }
119
+ };
120
+
121
+ const scanned = await analyzePR(
122
+ client,
123
+ "owner",
124
+ "repo",
125
+ prObject,
126
+ "@cursor",
127
+ hooks
128
+ );
129
+ ```
130
+
131
+ The `resolveStatus` hook receives:
132
+ - `coreStatus` — The determined core status
133
+ - `details` — `AnalysisDetails` with `comments`, `checks`, `reviews`, `ciStatus`, `hasConflicts`, `waitingOnBot`
134
+
135
+ ## PR Classification
136
+
137
+ Classify analyzed PRs into action buckets for workflow management.
138
+
139
+ ### `classifyPRs()`
140
+
141
+ Groups PRs into four buckets based on status:
142
+
143
+ ```typescript
144
+ import { classifyPRs } from "@hardlydifficult/pr-analyzer";
145
+
146
+ const result = classifyPRs(scannedPRs);
147
+
148
+ // result.readyForHuman — needs_review, changes_requested, approved, ready_to_merge
149
+ // result.needsBotBump — waiting_on_bot
150
+ // result.inProgress — ci_running
151
+ // result.blocked — draft, ci_failed, has_conflicts
152
+ // result.all — all PRs
153
+ ```
154
+
155
+ ### Extending Classification
156
+
157
+ Add custom statuses to buckets via `ClassificationConfig`:
158
+
159
+ ```typescript
160
+ const result = classifyPRs(scannedPRs, {
161
+ readyForHuman: ["custom_ready"],
162
+ inProgress: ["ai_processing"],
163
+ blocked: ["custom_blocked"],
164
+ needsBotBump: ["custom_waiting"]
165
+ });
166
+ ```
167
+
168
+ ## PR Actions
169
+
170
+ Determine which actions are available for a PR based on its status.
171
+
172
+ ### `getAvailableActions()`
173
+
174
+ Returns available actions for a PR:
175
+
176
+ ```typescript
177
+ import { getAvailableActions } from "@hardlydifficult/pr-analyzer";
178
+
179
+ const actions = getAvailableActions(scannedPR);
180
+
181
+ // Returns PRActionDescriptor[] with type, label, description
182
+ // Example: [{ type: "merge", label: "Merge", description: "Squash and merge this PR" }]
183
+ ```
184
+
185
+ **Core actions** (status-dependent):
186
+ - `merge` — Available for `ready_to_merge` or `approved` status
187
+ - `mark_ready` — Available for `draft` status when CI passed and no conflicts
188
+ - `enable_auto_merge` — Available for `ci_running` or `needs_review` status (non-draft, non-conflicting, unmerged)
189
+
190
+ ### `PR_ACTIONS`
191
+
192
+ Reference object for core action metadata:
193
+
194
+ ```typescript
195
+ import { PR_ACTIONS } from "@hardlydifficult/pr-analyzer";
196
+
197
+ console.log(PR_ACTIONS.merge);
198
+ // { label: "Merge", description: "Squash and merge this PR" }
199
+ ```
200
+
201
+ ### Custom Actions
202
+
203
+ Extend available actions with custom logic:
204
+
205
+ ```typescript
206
+ import type { ActionDefinition } from "@hardlydifficult/pr-analyzer";
207
+
208
+ const customActions: ActionDefinition[] = [
209
+ {
210
+ type: "fix_ci",
211
+ label: "Fix CI",
212
+ description: "Post @cursor fix CI comment",
213
+ when: (pr, ctx) => pr.status === "ci_failed" && ctx["isWorkPR"] === true
214
+ }
215
+ ];
216
+
217
+ const actions = getAvailableActions(scannedPR, customActions, {
218
+ isWorkPR: true
219
+ });
220
+ ```
221
+
222
+ The `when` function receives:
223
+ - `pr` — The `ScannedPR` object
224
+ - `context` — Key-value boolean flags for conditional logic
225
+
226
+ ## Setup
227
+
228
+ ### GitHub Authentication
229
+
230
+ Provide a GitHub token via environment variable or directly:
231
+
232
+ ```typescript
233
+ import { createGitHubClient } from "@hardlydifficult/github";
234
+
235
+ const client = createGitHubClient(process.env.GITHUB_TOKEN);
236
+ ```
237
+
238
+ ### Bot Mention
239
+
240
+ Pass the bot mention string (e.g., `"@cursor"`) to analysis functions. The analyzer detects when this mention appears in PR comments and tracks whether the bot has replied.
241
+
242
+ ### Logger (Optional)
243
+
244
+ Implement the `Logger` interface for error handling in `analyzeAll()`:
245
+
246
+ ```typescript
247
+ import type { Logger } from "@hardlydifficult/pr-analyzer";
248
+
249
+ const logger: Logger = {
250
+ info: (message, context) => console.log(message, context),
251
+ error: (message, context) => console.error(message, context)
252
+ };
253
+
254
+ await analyzeAll(prs, client, "@cursor", logger);
255
+ ```
256
+
257
+ ## Appendix
258
+
259
+ ### CI Status Determination
260
+
261
+ CI status is determined from GitHub check runs:
262
+
263
+ | Condition | Result |
264
+ |-----------|--------|
265
+ | Any check in `in_progress` or `queued` | `isRunning: true` |
266
+ | Any check with conclusion `failure`, `timed_out`, `cancelled`, or `action_required` | `hasFailed: true` |
267
+ | All checks with conclusion `success`, `skipped`, or `neutral` | `allPassed: true` |
268
+ | No checks | `allPassed: true`, `summary: "No CI checks"` |
269
+
270
+ ### Review Status Determination
271
+
272
+ Latest review per reviewer is considered. If multiple reviews from the same user exist, only the most recent is used.
273
+
274
+ ### Bot Detection
275
+
276
+ The analyzer recognizes these bot usernames (case-insensitive):
277
+ - `cursor`, `cursor-bot`
278
+ - `github-actions`, `github-actions[bot]`
279
+ - `dependabot`, `dependabot[bot]`
280
+ - `renovate`, `renovate[bot]`
281
+ - `codecov`, `codecov[bot]`
282
+ - `vercel`, `vercel[bot]`
283
+ - `claude`
284
+ - Any username ending with `[bot]` or containing `bot`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hardlydifficult/pr-analyzer",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "main": "./dist/index.js",
5
5
  "types": "./dist/index.d.ts",
6
6
  "files": [