@oss-scout/core 1.2.2 → 1.2.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.
@@ -108,6 +108,19 @@ export interface RecommendationInput {
108
108
  * instead of a competing-PR penalty.
109
109
  */
110
110
  ownPR: boolean;
111
+ /**
112
+ * The linked PR was already merged (#249 part B). The issue is effectively
113
+ * resolved, so it is a hard skip — surfacing it as a contribution
114
+ * opportunity is noise. Distinct from an open competing PR (which may be
115
+ * revivable). Defaults to false.
116
+ */
117
+ linkedPRMerged?: boolean;
118
+ /**
119
+ * The linked PR was closed without merging (#249 part B). The previous
120
+ * attempt was abandoned or rejected, so default to skip rather than
121
+ * re-surfacing it. Defaults to false.
122
+ */
123
+ linkedPRClosed?: boolean;
111
124
  notClaimed: boolean;
112
125
  clearRequirements: boolean;
113
126
  contributionGuidelinesFound: boolean;
@@ -37,7 +37,11 @@ export function deriveRecommendation(input) {
37
37
  if (!input.noExistingPR)
38
38
  notes.push(input.ownPR
39
39
  ? "Your PR is already in flight for this issue"
40
- : "Existing PR found for this issue");
40
+ : input.linkedPRMerged
41
+ ? "A PR for this issue was already merged"
42
+ : input.linkedPRClosed
43
+ ? "A PR for this issue was closed without merging"
44
+ : "Existing PR found for this issue");
41
45
  if (!input.notClaimed)
42
46
  notes.push("Issue appears to be claimed by someone");
43
47
  if (input.existingPRInconclusive) {
@@ -57,8 +61,16 @@ export function deriveRecommendation(input) {
57
61
  if (!input.contributionGuidelinesFound)
58
62
  notes.push("No CONTRIBUTING.md found");
59
63
  // Reasons to skip / approve.
60
- if (!input.noExistingPR)
61
- reasonsToSkip.push(input.ownPR ? "You already have a PR in flight" : "Has existing PR");
64
+ if (!input.noExistingPR) {
65
+ if (input.ownPR)
66
+ reasonsToSkip.push("You already have a PR in flight");
67
+ else if (input.linkedPRMerged)
68
+ reasonsToSkip.push("Linked PR already merged");
69
+ else if (input.linkedPRClosed)
70
+ reasonsToSkip.push("Linked PR closed without merge");
71
+ else
72
+ reasonsToSkip.push("Has existing PR");
73
+ }
62
74
  if (!input.notClaimed)
63
75
  reasonsToSkip.push("Already claimed");
64
76
  if (!input.projectIsActive && !input.projectCheckFailed)
@@ -92,6 +104,13 @@ export function deriveRecommendation(input) {
92
104
  if (input.issueClosed) {
93
105
  recommendation = "skip";
94
106
  }
107
+ else if (input.linkedPRMerged || input.linkedPRClosed) {
108
+ // The issue is resolved (merged) or its attempt was abandoned/rejected
109
+ // (closed) — a hard skip, not a revive opportunity (#249 part B). An OPEN
110
+ // competing PR is deliberately NOT caught here; it falls through to the
111
+ // existing competing-PR handling below.
112
+ recommendation = "skip";
113
+ }
95
114
  else if (input.ownPR) {
96
115
  // You're already working on this; don't re-surface it as competition.
97
116
  recommendation = "skip";
@@ -201,6 +220,12 @@ export class IssueVetter {
201
220
  !linkedPR.merged &&
202
221
  username !== "" &&
203
222
  linkedPR.author.toLowerCase() === username.toLowerCase();
223
+ // Linked-PR lifecycle gate (#249 part B): a merged linked PR means the
224
+ // issue is resolved; a closed-unmerged one means the attempt was
225
+ // abandoned/rejected. Both are hard skips. (state === "closed" && merged
226
+ // is how buildLinkedPRFromTimelineEvent encodes a merged PR.)
227
+ const linkedPRMerged = !!linkedPR && linkedPR.merged;
228
+ const linkedPRClosed = !!linkedPR && linkedPR.state === "closed" && !linkedPR.merged;
204
229
  // Analyze issue quality
205
230
  const clearRequirements = analyzeRequirements(core.body);
206
231
  // When the health check itself failed (API error), use a neutral default:
@@ -263,6 +288,8 @@ export class IssueVetter {
263
288
  const { notes, reasonsToApprove, reasonsToSkip, recommendation } = deriveRecommendation({
264
289
  noExistingPR,
265
290
  ownPR,
291
+ linkedPRMerged,
292
+ linkedPRClosed,
266
293
  notClaimed,
267
294
  clearRequirements,
268
295
  contributionGuidelinesFound: !!contributionGuidelines,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oss-scout/core",
3
- "version": "1.2.2",
3
+ "version": "1.2.3",
4
4
  "description": "Personalized GitHub issue finder with multi-strategy search, deep vetting, and viability scoring — CLI, library, MCP server, and Claude Code plugin",
5
5
  "type": "module",
6
6
  "bin": {