Skip to content

Commit 91314f0

Browse files
anjoolacursoragent
andauthored
Stop counting draft PRs as open Dependicus items for review (#63)
* Stop counting draft PRs as open Dependicus items for review The previous pass over-corrected and made searchDependicusIssues drop every pull request returned by GitHub's issues endpoint. That left notification bots with no way to distinguish a fresh, ready-for-review PR from a half-finished draft, and they kept honking '54 open Dependicus PRs' because they were still reaching for the count via other channels. Now the helper: - Skips draft PRs only — a draft is explicitly not open for review. - Returns ready-for-review PRs alongside regular issues, with a new isPullRequest flag so callers can tell them apart. - Still skips anything flagged as a draft defensively. The reconciler uses the new flag to keep treating PR titles as 'something already exists, don't create a duplicate', while never mutating the PR via octokit.issues.update (which would clobber the PR's title and body since GitHub gives PRs and issues a shared numbering scheme). So the goose finally stops honking at perfectly good ducks paddling in the review pond — only the ducklings still tucked under the wing get left alone. Co-authored-by: Angela Gong <anjoola@users.noreply.github.com> * Regenerate yarn.lock for upstream semver and undici releases CI's lockfile-consistency check started failing because semver@7.8.1 and undici@6.26.0 were published upstream and the ^7.3.5 / ^6.25.0 ranges now resolve to the newer versions. Running 'mise update-all-lockfiles' produces only this lockfile refresh — no source changes. Co-authored-by: Angela Gong <anjoola@users.noreply.github.com> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com>
1 parent 0811034 commit 91314f0

5 files changed

Lines changed: 61 additions & 22 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
### Changed
1010

11-
- `searchDependicusIssues` (in `@dependicus/github-issues`) now skips every pull request returned by GitHub's issues endpoint — drafts and ready-to-review alike — and also skips anything flagged as a draft. Only real, non-draft issues are returned, so notification bots and reports built on this helper stop counting pull requests as open Dependicus items.
11+
- `searchDependicusIssues` (in `@dependicus/github-issues`) now treats draft pull requests as not yet open for review and excludes them from results, while ready-for-review pull requests are returned alongside regular issues. Each returned entry carries an `isPullRequest` boolean so notification bots can count open Dependicus items accurately — drafts no longer pad the total — and the reconciler can avoid mutating pull requests. Anything explicitly flagged as a draft (PR or otherwise) is still skipped defensively.
1212

1313
### Fixed
1414

src/github-issues/GitHubIssueService.test.ts

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,10 +99,11 @@ describe('GitHubIssueService', () => {
9999
dependencyName: 'react',
100100
isGroup: false,
101101
updatedAt: '2025-01-15T00:00:00Z',
102+
isPullRequest: false,
102103
});
103104
});
104105

105-
it('skips pull requests regardless of draft state', async () => {
106+
it('skips draft PRs and includes ready-for-review PRs flagged as pull requests', async () => {
106107
mockOctokit.issues.getLabel.mockResolvedValue({ data: { name: 'dependicus' } });
107108

108109
mockOctokit.issues.listForRepo.mockResolvedValue({
@@ -125,7 +126,27 @@ describe('GitHubIssueService', () => {
125126
});
126127

127128
const issues = await service.searchDependicusIssues('owner', 'repo');
128-
expect(issues).toHaveLength(0);
129+
expect(issues).toHaveLength(1);
130+
expect(issues[0]!.number).toBe(43);
131+
expect(issues[0]!.isPullRequest).toBe(true);
132+
});
133+
134+
it('flags non-PR matches as not being a pull request', async () => {
135+
mockOctokit.issues.getLabel.mockResolvedValue({ data: { name: 'dependicus' } });
136+
137+
mockOctokit.issues.listForRepo.mockResolvedValue({
138+
data: [
139+
{
140+
number: 7,
141+
title: '[Dependicus] Update react from 18.2.0 to 19.0.0',
142+
updated_at: '2025-01-15T00:00:00Z',
143+
},
144+
],
145+
});
146+
147+
const issues = await service.searchDependicusIssues('owner', 'repo');
148+
expect(issues).toHaveLength(1);
149+
expect(issues[0]!.isPullRequest).toBe(false);
129150
});
130151

131152
it('skips items flagged as draft', async () => {

src/github-issues/GitHubIssueService.ts

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ const DEPENDICUS_LABEL_NAME = 'dependicus';
55
const TITLE_PREFIX = '[Dependicus]';
66

77
export interface DependicusIssue {
8-
/** GitHub issue number */
8+
/** GitHub issue number (or pull request number — they share a numbering scheme) */
99
number: number;
1010
title: string;
1111
/** Issue body (markdown description) */
@@ -21,6 +21,13 @@ export interface DependicusIssue {
2121
isGroup: boolean;
2222
/** ISO date string when the issue was last updated */
2323
updatedAt: string;
24+
/**
25+
* True when this entry is a pull request. Drafts are filtered out by
26+
* `searchDependicusIssues`, so any PR here is ready-for-review. PRs are
27+
* surfaced so notification bots can count open Dependicus items
28+
* accurately, but the reconciler must avoid mutating them.
29+
*/
30+
isPullRequest: boolean;
2431
}
2532

2633
export interface CreateIssueParams {
@@ -78,15 +85,18 @@ export class GitHubIssueService {
7885
}
7986

8087
/**
81-
* Search for all open Dependicus issues in a repo.
88+
* Search for all open Dependicus items in a repo.
8289
* Filters by the "dependicus" label and state=open.
83-
* Handles pagination to ensure ALL issues are fetched.
90+
* Handles pagination to ensure ALL items are fetched.
8491
*
8592
* GitHub's issues endpoint returns pull requests alongside issues when
86-
* filtered by label. Pull requests are always skipped (regardless of
87-
* draft state) so that callers only see real issues, and any item with
88-
* a draft flag is skipped defensively so downstream consumers (such as
89-
* the bot that yells about the open count) never see in-progress work.
93+
* filtered by label. Draft pull requests are skipped because a draft is
94+
* explicitly not "open for review" — the bot that yells about the open
95+
* Dependicus count shouldn't honk at half-finished work. Ready-for-review
96+
* pull requests and regular issues are both returned (with
97+
* `isPullRequest` set accordingly) so they can be counted as legitimately
98+
* open. Anything flagged as a draft (PR or otherwise) is also skipped
99+
* defensively.
90100
*/
91101
async searchDependicusIssues(
92102
owner: string,
@@ -109,10 +119,10 @@ export class GitHubIssueService {
109119
});
110120

111121
for (const issue of response.data) {
112-
// GitHub returns PRs in the issues endpoint — skip every PR
113-
// (draft or not). Also skip anything explicitly flagged as
114-
// a draft so non-PR draft items can't slip through either.
115-
if (issue.pull_request || issue.draft) continue;
122+
// Draft items (PRs or otherwise) are not open for review yet,
123+
// so they don't count toward the open total. Non-draft PRs
124+
// and regular issues both flow through.
125+
if (issue.draft) continue;
116126

117127
const groupName = extractGroupNameFromTitle(issue.title);
118128
const dependencyName = groupName ?? extractDependencyNameFromTitle(issue.title);
@@ -125,6 +135,7 @@ export class GitHubIssueService {
125135
dependencyName,
126136
isGroup: groupName !== undefined,
127137
updatedAt: issue.updated_at,
138+
isPullRequest: Boolean(issue.pull_request),
128139
});
129140
}
130141

@@ -174,6 +185,7 @@ export class GitHubIssueService {
174185
dependencyName: extractedName,
175186
isGroup: groupName !== undefined,
176187
updatedAt: item.updated_at,
188+
isPullRequest: false,
177189
};
178190
}
179191

src/github-issues/issueReconciler.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -472,18 +472,24 @@ export async function reconcileGitHubIssues(
472472
);
473473
process.stderr.write(`Found ${existingIssues.length} existing issues\n`);
474474

475-
// Build maps for deduplication
475+
// Build maps for deduplication. Pull requests (always ready-for-review at
476+
// this point — drafts are filtered upstream) are recorded by title so we
477+
// don't create a duplicate issue when an agent has already opened a PR,
478+
// but they're excluded from the by-dependency map so the reconciler never
479+
// mutates them like an issue (an `octokit.issues.update` against a PR
480+
// number would clobber the PR's title and body).
476481
const existingIssuesByDependency = new Map<string, DependicusIssue>();
477482
const existingIssuesByTitle = new Set<string>();
478483
const duplicateIssues: DependicusIssue[] = [];
479484

480485
for (const issue of existingIssues) {
486+
existingIssuesByTitle.add(issue.title);
487+
if (issue.isPullRequest) continue;
481488
if (!existingIssuesByDependency.has(issue.dependencyName)) {
482489
existingIssuesByDependency.set(issue.dependencyName, issue);
483490
} else {
484491
duplicateIssues.push(issue);
485492
}
486-
existingIssuesByTitle.add(issue.title);
487493
}
488494

489495
// Close duplicate issues proactively

yarn.lock

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2656,11 +2656,11 @@ __metadata:
26562656
linkType: hard
26572657

26582658
"semver@npm:^7.3.5":
2659-
version: 7.8.0
2660-
resolution: "semver@npm:7.8.0"
2659+
version: 7.8.1
2660+
resolution: "semver@npm:7.8.1"
26612661
bin:
26622662
semver: bin/semver.js
2663-
checksum: 10c0/8f096ca9b80ffd47b308d03f9ce8c873e27e2983f36023c559cdc92c51e8433fc23ebbfe57ec9623fc155636a6961ee989501099841ae4bb1babc8d2b3f048cd
2663+
checksum: 10c0/92d6871d6347e1f99d0ba396a70f2545ccf2a032cda3d378fa0699edf7506b5c6d266aed55c8b88e72bd91a30d2351e4f39db479375374430fcdc4b58f4e3c1a
26642664
languageName: node
26652665
linkType: hard
26662666

@@ -2975,9 +2975,9 @@ __metadata:
29752975
linkType: hard
29762976

29772977
"undici@npm:^6.25.0":
2978-
version: 6.25.0
2979-
resolution: "undici@npm:6.25.0"
2980-
checksum: 10c0/2597cc6689bdb02c210c557b1f85febbfda65becae6e6fc1061508e2f33734d25207f81cd8af56ada9956329eb3a7bd7431e87dcfeceba20ee87059b57dcf985
2978+
version: 6.26.0
2979+
resolution: "undici@npm:6.26.0"
2980+
checksum: 10c0/cf2b4caf58c33d6582970991290cc7a6486d6e738845f25dcdd16952d708ec844815c6d30362919764fcaf30f719891289341f1ada496f003ce2700310453a47
29812981
languageName: node
29822982
linkType: hard
29832983

0 commit comments

Comments
 (0)