github-issue-tower-defence-management 1.104.1 → 1.104.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.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,10 @@
1
+ ## [1.104.2](https://github.com/HiromiShikata/npm-cli-github-issue-tower-defence-management/compare/v1.104.1...v1.104.2) (2026-06-26)
2
+
3
+
4
+ ### Performance Improvements
5
+
6
+ * **schedule:** derive review-readiness related open PRs in memory ([#989](https://github.com/HiromiShikata/npm-cli-github-issue-tower-defence-management/issues/989)) ([63dba8b](https://github.com/HiromiShikata/npm-cli-github-issue-tower-defence-management/commit/63dba8b2ddf0b813873337e0e4292e51493383fb)), closes [#988](https://github.com/HiromiShikata/npm-cli-github-issue-tower-defence-management/issues/988)
7
+
1
8
  ## [1.104.1](https://github.com/HiromiShikata/npm-cli-github-issue-tower-defence-management/compare/v1.104.0...v1.104.1) (2026-06-26)
2
9
 
3
10
 
@@ -4,7 +4,7 @@ exports.IssueRejectionEvaluator = void 0;
4
4
  class IssueRejectionEvaluator {
5
5
  constructor(issueRepository) {
6
6
  this.issueRepository = issueRepository;
7
- this.evaluate = async (issue, labelsAsLlmAgentName = []) => {
7
+ this.evaluate = async (issue, labelsAsLlmAgentName = [], options = {}) => {
8
8
  const rejections = [];
9
9
  let approvedPrUrl = null;
10
10
  const categoryLabels = issue.labels.filter((label) => label.startsWith('category:'));
@@ -15,7 +15,9 @@ class IssueRejectionEvaluator {
15
15
  (categoryLabels.length <= 0 || categoryLabels.includes('category:e2e'))) {
16
16
  const prsToCheck = issue.isPr
17
17
  ? await this.resolveOpenPrsForPrItem(issue.url)
18
- : await this.issueRepository.findRelatedOpenPRs(issue.url);
18
+ : options.relatedOpenPrUrls != null
19
+ ? await this.resolveOpenPrsFromUrls(options.relatedOpenPrUrls)
20
+ : await this.issueRepository.findRelatedOpenPRs(issue.url);
19
21
  if (prsToCheck.length <= 0) {
20
22
  rejections.push({
21
23
  type: 'PULL_REQUEST_NOT_FOUND',
@@ -100,6 +102,23 @@ class IssueRejectionEvaluator {
100
102
  }
101
103
  return [pr];
102
104
  };
105
+ // Resolves the status of each prebuilt related PR URL via getOpenPullRequest,
106
+ // dropping any URL whose PR is not open (getOpenPullRequest returns null).
107
+ // This mirrors findRelatedOpenPRs, which also returns only OPEN pull requests,
108
+ // so the resulting set is equivalent while avoiding the per-issue timeline
109
+ // query. Duplicate URLs are de-duplicated to mirror findRelatedOpenPRs, which
110
+ // collapses duplicates via its internal Map keyed by PR URL.
111
+ this.resolveOpenPrsFromUrls = async (prUrls) => {
112
+ const uniquePrUrls = Array.from(new Set(prUrls));
113
+ const resolvedPrs = [];
114
+ for (const prUrl of uniquePrUrls) {
115
+ const pr = await this.issueRepository.getOpenPullRequest(prUrl);
116
+ if (pr !== null) {
117
+ resolvedPrs.push(pr);
118
+ }
119
+ }
120
+ return resolvedPrs;
121
+ };
103
122
  this.extractChangeTargetMustPaths = (labels) => {
104
123
  const prefix = 'change-target-must:';
105
124
  const paths = [];
@@ -1 +1 @@
1
- {"version":3,"file":"IssueRejectionEvaluator.js","sourceRoot":"","sources":["../../../src/domain/usecases/IssueRejectionEvaluator.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"IssueRejectionEvaluator.js","sourceRoot":"","sources":["../../../src/domain/usecases/IssueRejectionEvaluator.ts"],"names":[],"mappings":"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"}
@@ -34,6 +34,7 @@ class RevertNotReadyReviewQueueIssueUseCase {
34
34
  }
35
35
  const { issues } = await this.issueRepository.getAllIssues(projectId, params.allowIssueCacheMinutes);
36
36
  const awaitingQualityCheckIssues = issues.filter((issue) => issue.status === WorkflowStatus_1.AWAITING_QUALITY_CHECK_STATUS_NAME);
37
+ const relatedOpenPrUrlsByIssueUrl = this.buildRelatedOpenPrUrlsByIssueUrl(issues);
37
38
  for (const issue of awaitingQualityCheckIssues) {
38
39
  const hasLlmAgentLabel = issue.labels.some((l) => l === 'llm-agent' || l.startsWith('llm-agent:'));
39
40
  if (hasLlmAgentLabel) {
@@ -42,7 +43,9 @@ class RevertNotReadyReviewQueueIssueUseCase {
42
43
  if (!isAuthorAuthorizedForAutoStatusCheck(issue.author, allowedIssueAuthors)) {
43
44
  continue;
44
45
  }
45
- const { rejections, approvedPrUrl } = await this.issueRejectionEvaluator.evaluate(issue, params.labelsAsLlmAgentName ?? []);
46
+ const { rejections, approvedPrUrl } = await this.issueRejectionEvaluator.evaluate(issue, params.labelsAsLlmAgentName ?? [], {
47
+ relatedOpenPrUrls: relatedOpenPrUrlsByIssueUrl.get(issue.url) ?? [],
48
+ });
46
49
  if (rejections.length > 0) {
47
50
  await this.issueRepository.updateStatus(project, issue, awaitingWorkspaceStatusOption.id);
48
51
  await this.issueCommentRepository.createComment(issue, `Auto Status Check: REJECTED\n${rejections.map((r) => `- ${r.detail}`).join('\n')}`);
@@ -70,6 +73,34 @@ class RevertNotReadyReviewQueueIssueUseCase {
70
73
  }
71
74
  }
72
75
  };
76
+ // Derives, for each issue, the set of open pull request URLs that reference it
77
+ // via a closing keyword. The linkage is taken from each open PR item's
78
+ // closingIssueReferenceUrls (populated in bulk by fetchProjectItems), the same
79
+ // in-memory derivation SetDependedIssueUrlForOpenTaskPRsUseCase uses. This
80
+ // replaces the per-issue findRelatedOpenPRs timeline query in the
81
+ // review-readiness sweep with a single in-memory pass over the bulk items.
82
+ this.buildRelatedOpenPrUrlsByIssueUrl = (issues) => {
83
+ const openPrUrlsByIssueUrl = new Map();
84
+ for (const issue of issues) {
85
+ if (!issue.isPr || issue.isClosed) {
86
+ continue;
87
+ }
88
+ for (const referencedIssueUrl of issue.closingIssueReferenceUrls) {
89
+ const existing = openPrUrlsByIssueUrl.get(referencedIssueUrl);
90
+ if (existing) {
91
+ existing.add(issue.url);
92
+ }
93
+ else {
94
+ openPrUrlsByIssueUrl.set(referencedIssueUrl, new Set([issue.url]));
95
+ }
96
+ }
97
+ }
98
+ const result = new Map();
99
+ for (const [issueUrl, prUrls] of openPrUrlsByIssueUrl) {
100
+ result.set(issueUrl, Array.from(prUrls));
101
+ }
102
+ return result;
103
+ };
73
104
  this.issueRejectionEvaluator = new IssueRejectionEvaluator_1.IssueRejectionEvaluator(issueRepository);
74
105
  this.changeTargetPullRequestApprover = new ChangeTargetPullRequestApprover_1.ChangeTargetPullRequestApprover(issueRepository);
75
106
  }
@@ -1 +1 @@
1
- {"version":3,"file":"RevertNotReadyReviewQueueIssueUseCase.js","sourceRoot":"","sources":["../../../src/domain/usecases/RevertNotReadyReviewQueueIssueUseCase.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"RevertNotReadyReviewQueueIssueUseCase.js","sourceRoot":"","sources":["../../../src/domain/usecases/RevertNotReadyReviewQueueIssueUseCase.ts"],"names":[],"mappings":";;;AAIA,uEAAoE;AACpE,uFAAoF;AACpF,+DAIoC;AAEpC,MAAM,oCAAoC,GAAG,CAC3C,MAAc,EACd,mBAAgD,EACvC,EAAE;IACX,IAAI,mBAAmB,KAAK,IAAI,IAAI,mBAAmB,KAAK,SAAS,EAAE,CAAC;QACtE,OAAO,KAAK,CAAC;IACf,CAAC;IACD,IAAI,mBAAmB,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACrC,OAAO,KAAK,CAAC;IACf,CAAC;IACD,OAAO,mBAAmB,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;AAC9C,CAAC,CAAC;AAEF,MAAa,qCAAqC;IAIhD,YACmB,iBAGhB,EACgB,eAUhB,EACgB,sBAGhB;QAlBgB,sBAAiB,GAAjB,iBAAiB,CAGjC;QACgB,oBAAe,GAAf,eAAe,CAU/B;QACgB,2BAAsB,GAAtB,sBAAsB,CAGtC;QAQH,QAAG,GAAG,KAAK,EAAE,MAMZ,EAAiB,EAAE;YAClB,MAAM,mBAAmB,GAAG,MAAM,CAAC,mBAAmB,IAAI,IAAI,CAAC;YAC/D,MAAM,SAAS,GAAG,MAAM,IAAI,CAAC,iBAAiB,CAAC,kBAAkB,CAC/D,MAAM,CAAC,UAAU,CAClB,CAAC;YACF,IAAI,CAAC,SAAS,EAAE,CAAC;gBACf,MAAM,IAAI,KAAK,CAAC,kCAAkC,MAAM,CAAC,UAAU,EAAE,CAAC,CAAC;YACzE,CAAC;YACD,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,iBAAiB,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC;YACnE,IAAI,CAAC,OAAO,EAAE,CAAC;gBACb,MAAM,IAAI,KAAK,CACb,iCAAiC,SAAS,gBAAgB,MAAM,CAAC,UAAU,EAAE,CAC9E,CAAC;YACJ,CAAC;YAED,MAAM,6BAA6B,GAAG,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAChE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,+CAA8B,CACjD,CAAC;YACF,IAAI,CAAC,6BAA6B,EAAE,CAAC;gBACnC,OAAO;YACT,CAAC;YAED,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,IAAI,CAAC,eAAe,CAAC,YAAY,CACxD,SAAS,EACT,MAAM,CAAC,sBAAsB,CAC9B,CAAC;YAEF,MAAM,0BAA0B,GAAG,MAAM,CAAC,MAAM,CAC9C,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,KAAK,mDAAkC,CAC/D,CAAC;YAEF,MAAM,2BAA2B,GAC/B,IAAI,CAAC,gCAAgC,CAAC,MAAM,CAAC,CAAC;YAEhD,KAAK,MAAM,KAAK,IAAI,0BAA0B,EAAE,CAAC;gBAC/C,MAAM,gBAAgB,GAAG,KAAK,CAAC,MAAM,CAAC,IAAI,CACxC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,WAAW,IAAI,CAAC,CAAC,UAAU,CAAC,YAAY,CAAC,CACvD,CAAC;gBACF,IAAI,gBAAgB,EAAE,CAAC;oBACrB,SAAS;gBACX,CAAC;gBAED,IACE,CAAC,oCAAoC,CAAC,KAAK,CAAC,MAAM,EAAE,mBAAmB,CAAC,EACxE,CAAC;oBACD,SAAS;gBACX,CAAC;gBAED,MAAM,EAAE,UAAU,EAAE,aAAa,EAAE,GACjC,MAAM,IAAI,CAAC,uBAAuB,CAAC,QAAQ,CACzC,KAAK,EACL,MAAM,CAAC,oBAAoB,IAAI,EAAE,EACjC;oBACE,iBAAiB,EAAE,2BAA2B,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE;iBACpE,CACF,CAAC;gBACJ,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;oBAC1B,MAAM,IAAI,CAAC,eAAe,CAAC,YAAY,CACrC,OAAO,EACP,KAAK,EACL,6BAA6B,CAAC,EAAE,CACjC,CAAC;oBACF,MAAM,IAAI,CAAC,sBAAsB,CAAC,aAAa,CAC7C,KAAK,EACL,gCAAgC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CACpF,CAAC;oBACF,SAAS;gBACX,CAAC;gBAED,MAAM,IAAI,CAAC,+BAA+B,CAAC,iBAAiB,CAC1D,KAAK,CAAC,MAAM,EACZ,aAAa,EACb,MAAM,CAAC,uBAAuB,CAC/B,CAAC;YACJ,CAAC;YAED,MAAM,YAAY,GAAG,OAAO,CAAC,KAAK,CAAC;YACnC,MAAM,kBAAkB,GAAG,MAAM,CAAC,MAAM,CACtC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,KAAK,oCAAmB,IAAI,KAAK,CAAC,IAAI,CAC9D,CAAC;YAEF,KAAK,MAAM,WAAW,IAAI,kBAAkB,EAAE,CAAC;gBAC7C,MAAM,gBAAgB,GAAG,WAAW,CAAC,MAAM,CAAC,IAAI,CAC9C,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,WAAW,IAAI,CAAC,CAAC,UAAU,CAAC,YAAY,CAAC,CACvD,CAAC;gBACF,IAAI,gBAAgB,EAAE,CAAC;oBACrB,SAAS;gBACX,CAAC;gBAED,IACE,CAAC,oCAAoC,CACnC,WAAW,CAAC,MAAM,EAClB,mBAAmB,CACpB,EACD,CAAC;oBACD,SAAS;gBACX,CAAC;gBAED,MAAM,EAAE,UAAU,EAAE,GAAG,MAAM,IAAI,CAAC,uBAAuB,CAAC,QAAQ,CAChE,WAAW,EACX,MAAM,CAAC,oBAAoB,IAAI,EAAE,CAClC,CAAC;gBACF,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;oBAC1B,MAAM,IAAI,CAAC,eAAe,CAAC,YAAY,CACrC,OAAO,EACP,WAAW,EACX,6BAA6B,CAAC,EAAE,CACjC,CAAC;oBACF,IAAI,YAAY,EAAE,CAAC;wBACjB,MAAM,IAAI,CAAC,eAAe,CAAC,WAAW,CACpC,EAAE,GAAG,OAAO,EAAE,KAAK,EAAE,YAAY,EAAE,EACnC,WAAW,EACX,YAAY,CAAC,uBAAuB,CAAC,EAAE,CACxC,CAAC;oBACJ,CAAC;oBACD,MAAM,IAAI,CAAC,sBAAsB,CAAC,aAAa,CAC7C,WAAW,EACX,gCAAgC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CACpF,CAAC;gBACJ,CAAC;YACH,CAAC;QACH,CAAC,CAAC;QAEF,+EAA+E;QAC/E,uEAAuE;QACvE,+EAA+E;QAC/E,2EAA2E;QAC3E,kEAAkE;QAClE,2EAA2E;QACnE,qCAAgC,GAAG,CACzC,MAAe,EACQ,EAAE;YACzB,MAAM,oBAAoB,GAAG,IAAI,GAAG,EAAuB,CAAC;YAC5D,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;gBAC3B,IAAI,CAAC,KAAK,CAAC,IAAI,IAAI,KAAK,CAAC,QAAQ,EAAE,CAAC;oBAClC,SAAS;gBACX,CAAC;gBACD,KAAK,MAAM,kBAAkB,IAAI,KAAK,CAAC,yBAAyB,EAAE,CAAC;oBACjE,MAAM,QAAQ,GAAG,oBAAoB,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC;oBAC9D,IAAI,QAAQ,EAAE,CAAC;wBACb,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;oBAC1B,CAAC;yBAAM,CAAC;wBACN,oBAAoB,CAAC,GAAG,CAAC,kBAAkB,EAAE,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;oBACrE,CAAC;gBACH,CAAC;YACH,CAAC;YACD,MAAM,MAAM,GAAG,IAAI,GAAG,EAAoB,CAAC;YAC3C,KAAK,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,IAAI,oBAAoB,EAAE,CAAC;gBACtD,MAAM,CAAC,GAAG,CAAC,QAAQ,EAAE,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC;YAC3C,CAAC;YACD,OAAO,MAAM,CAAC;QAChB,CAAC,CAAC;QAnKA,IAAI,CAAC,uBAAuB,GAAG,IAAI,iDAAuB,CAAC,eAAe,CAAC,CAAC;QAC5E,IAAI,CAAC,+BAA+B,GAAG,IAAI,iEAA+B,CACxE,eAAe,CAChB,CAAC;IACJ,CAAC;CAgKF;AA7LD,sFA6LC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "github-issue-tower-defence-management",
3
- "version": "1.104.1",
3
+ "version": "1.104.2",
4
4
  "description": "",
5
5
  "main": "bin/index.js",
6
6
  "scripts": {
@@ -205,6 +205,188 @@ describe('IssueRejectionEvaluator', () => {
205
205
  expect(result.rejections[0].type).toBe('PULL_REQUEST_NOT_FOUND');
206
206
  });
207
207
 
208
+ describe('prebuilt relatedOpenPrUrls (in-memory derivation path)', () => {
209
+ it('should resolve PR status via getOpenPullRequest and not call findRelatedOpenPRs when relatedOpenPrUrls is provided', async () => {
210
+ mockIssueRepository.getOpenPullRequest.mockResolvedValue(
211
+ createReadyPr('https://github.com/user/repo/pull/7'),
212
+ );
213
+
214
+ const result = await evaluator.evaluate(
215
+ {
216
+ url: 'https://github.com/user/repo/issues/1',
217
+ labels: [],
218
+ isPr: false,
219
+ },
220
+ [],
221
+ { relatedOpenPrUrls: ['https://github.com/user/repo/pull/7'] },
222
+ );
223
+
224
+ expect(mockIssueRepository.findRelatedOpenPRs).not.toHaveBeenCalled();
225
+ expect(mockIssueRepository.getOpenPullRequest).toHaveBeenCalledWith(
226
+ 'https://github.com/user/repo/pull/7',
227
+ );
228
+ expect(result.rejections).toHaveLength(0);
229
+ expect(result.approvedPrUrl).toBe(
230
+ 'https://github.com/user/repo/pull/7',
231
+ );
232
+ });
233
+
234
+ it('should reject with PULL_REQUEST_NOT_FOUND when relatedOpenPrUrls is empty', async () => {
235
+ const result = await evaluator.evaluate(
236
+ {
237
+ url: 'https://github.com/user/repo/issues/1',
238
+ labels: [],
239
+ isPr: false,
240
+ },
241
+ [],
242
+ { relatedOpenPrUrls: [] },
243
+ );
244
+
245
+ expect(mockIssueRepository.findRelatedOpenPRs).not.toHaveBeenCalled();
246
+ expect(mockIssueRepository.getOpenPullRequest).not.toHaveBeenCalled();
247
+ expect(result.rejections).toHaveLength(1);
248
+ expect(result.rejections[0].type).toBe('PULL_REQUEST_NOT_FOUND');
249
+ expect(result.approvedPrUrl).toBeNull();
250
+ });
251
+
252
+ it('should drop a related URL whose PR is no longer open (getOpenPullRequest returns null), yielding PULL_REQUEST_NOT_FOUND', async () => {
253
+ mockIssueRepository.getOpenPullRequest.mockResolvedValue(null);
254
+
255
+ const result = await evaluator.evaluate(
256
+ {
257
+ url: 'https://github.com/user/repo/issues/1',
258
+ labels: [],
259
+ isPr: false,
260
+ },
261
+ [],
262
+ { relatedOpenPrUrls: ['https://github.com/user/repo/pull/9'] },
263
+ );
264
+
265
+ expect(mockIssueRepository.findRelatedOpenPRs).not.toHaveBeenCalled();
266
+ expect(mockIssueRepository.getOpenPullRequest).toHaveBeenCalledWith(
267
+ 'https://github.com/user/repo/pull/9',
268
+ );
269
+ expect(result.rejections).toHaveLength(1);
270
+ expect(result.rejections[0].type).toBe('PULL_REQUEST_NOT_FOUND');
271
+ });
272
+
273
+ it('should reject with MULTIPLE_PULL_REQUESTS_FOUND when more than one related open PR resolves', async () => {
274
+ mockIssueRepository.getOpenPullRequest.mockImplementation(
275
+ (prUrl: string) => Promise.resolve(createReadyPr(prUrl)),
276
+ );
277
+
278
+ const result = await evaluator.evaluate(
279
+ {
280
+ url: 'https://github.com/user/repo/issues/1',
281
+ labels: [],
282
+ isPr: false,
283
+ },
284
+ [],
285
+ {
286
+ relatedOpenPrUrls: [
287
+ 'https://github.com/user/repo/pull/1',
288
+ 'https://github.com/user/repo/pull/2',
289
+ ],
290
+ },
291
+ );
292
+
293
+ expect(mockIssueRepository.findRelatedOpenPRs).not.toHaveBeenCalled();
294
+ expect(result.rejections).toHaveLength(1);
295
+ expect(result.rejections[0].type).toBe('MULTIPLE_PULL_REQUESTS_FOUND');
296
+ });
297
+
298
+ it('should de-duplicate repeated related URLs before resolving, mirroring findRelatedOpenPRs', async () => {
299
+ mockIssueRepository.getOpenPullRequest.mockResolvedValue(
300
+ createReadyPr('https://github.com/user/repo/pull/5'),
301
+ );
302
+
303
+ const result = await evaluator.evaluate(
304
+ {
305
+ url: 'https://github.com/user/repo/issues/1',
306
+ labels: [],
307
+ isPr: false,
308
+ },
309
+ [],
310
+ {
311
+ relatedOpenPrUrls: [
312
+ 'https://github.com/user/repo/pull/5',
313
+ 'https://github.com/user/repo/pull/5',
314
+ ],
315
+ },
316
+ );
317
+
318
+ expect(mockIssueRepository.getOpenPullRequest).toHaveBeenCalledTimes(1);
319
+ expect(result.rejections).toHaveLength(0);
320
+ expect(result.approvedPrUrl).toBe(
321
+ 'https://github.com/user/repo/pull/5',
322
+ );
323
+ });
324
+
325
+ it('should produce the same draft rejection through the prebuilt path as through findRelatedOpenPRs', async () => {
326
+ mockIssueRepository.getOpenPullRequest.mockResolvedValue(
327
+ createReadyPr('https://github.com/user/repo/pull/3', {
328
+ isDraft: true,
329
+ }),
330
+ );
331
+
332
+ const result = await evaluator.evaluate(
333
+ {
334
+ url: 'https://github.com/user/repo/issues/1',
335
+ labels: [],
336
+ isPr: false,
337
+ },
338
+ [],
339
+ { relatedOpenPrUrls: ['https://github.com/user/repo/pull/3'] },
340
+ );
341
+
342
+ expect(result.rejections).toHaveLength(1);
343
+ expect(result.rejections[0].type).toBe('PULL_REQUEST_IS_DRAFT');
344
+ expect(result.approvedPrUrl).toBeNull();
345
+ });
346
+
347
+ it('should still use findRelatedOpenPRs when relatedOpenPrUrls is undefined (single-issue path unchanged)', async () => {
348
+ mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([
349
+ createReadyPr(),
350
+ ]);
351
+
352
+ const result = await evaluator.evaluate({
353
+ url: 'https://github.com/user/repo/issues/1',
354
+ labels: [],
355
+ isPr: false,
356
+ });
357
+
358
+ expect(mockIssueRepository.findRelatedOpenPRs).toHaveBeenCalledWith(
359
+ 'https://github.com/user/repo/issues/1',
360
+ );
361
+ expect(result.rejections).toHaveLength(0);
362
+ });
363
+
364
+ it('should ignore relatedOpenPrUrls for a PR item (isPr=true) and use getOpenPullRequest on the PR URL itself', async () => {
365
+ mockIssueRepository.getOpenPullRequest.mockResolvedValue(
366
+ createReadyPr('https://github.com/user/repo/pull/10'),
367
+ );
368
+
369
+ const result = await evaluator.evaluate(
370
+ {
371
+ url: 'https://github.com/user/repo/pull/10',
372
+ labels: [],
373
+ isPr: true,
374
+ },
375
+ [],
376
+ { relatedOpenPrUrls: ['https://github.com/user/repo/pull/999'] },
377
+ );
378
+
379
+ expect(mockIssueRepository.findRelatedOpenPRs).not.toHaveBeenCalled();
380
+ expect(mockIssueRepository.getOpenPullRequest).toHaveBeenCalledWith(
381
+ 'https://github.com/user/repo/pull/10',
382
+ );
383
+ expect(mockIssueRepository.getOpenPullRequest).not.toHaveBeenCalledWith(
384
+ 'https://github.com/user/repo/pull/999',
385
+ );
386
+ expect(result.rejections).toHaveLength(0);
387
+ });
388
+ });
389
+
208
390
  describe('change-target-must: label behavior', () => {
209
391
  const prUrl = 'https://github.com/user/repo/pull/1';
210
392
 
@@ -18,6 +18,17 @@ export type PrRejectionResult = {
18
18
  approvedPrUrl: string | null;
19
19
  };
20
20
 
21
+ export type EvaluateOptions = {
22
+ // When provided, the related open pull requests for a non-PR issue are
23
+ // resolved from these prebuilt URLs via getOpenPullRequest instead of
24
+ // discovering them with a per-issue findRelatedOpenPRs timeline query.
25
+ // Callers that already hold the bulk project items (e.g. the per-cycle
26
+ // review-readiness sweep) derive these URLs in memory from PR items'
27
+ // closingIssueReferenceUrls, eliminating the per-issue timeline fan-out.
28
+ // When null or undefined, behaviour is unchanged: findRelatedOpenPRs runs.
29
+ relatedOpenPrUrls?: string[] | null;
30
+ };
31
+
21
32
  export class IssueRejectionEvaluator {
22
33
  constructor(
23
34
  private readonly issueRepository: Pick<
@@ -36,6 +47,7 @@ export class IssueRejectionEvaluator {
36
47
  isPr: boolean;
37
48
  },
38
49
  labelsAsLlmAgentName: string[] = [],
50
+ options: EvaluateOptions = {},
39
51
  ): Promise<PrRejectionResult> => {
40
52
  const rejections: { type: PrRejectedReasonType; detail: string }[] = [];
41
53
  let approvedPrUrl: string | null = null;
@@ -57,7 +69,9 @@ export class IssueRejectionEvaluator {
57
69
  ) {
58
70
  const prsToCheck = issue.isPr
59
71
  ? await this.resolveOpenPrsForPrItem(issue.url)
60
- : await this.issueRepository.findRelatedOpenPRs(issue.url);
72
+ : options.relatedOpenPrUrls != null
73
+ ? await this.resolveOpenPrsFromUrls(options.relatedOpenPrUrls)
74
+ : await this.issueRepository.findRelatedOpenPRs(issue.url);
61
75
 
62
76
  if (prsToCheck.length <= 0) {
63
77
  rejections.push({
@@ -160,6 +174,26 @@ export class IssueRejectionEvaluator {
160
174
  return [pr];
161
175
  };
162
176
 
177
+ // Resolves the status of each prebuilt related PR URL via getOpenPullRequest,
178
+ // dropping any URL whose PR is not open (getOpenPullRequest returns null).
179
+ // This mirrors findRelatedOpenPRs, which also returns only OPEN pull requests,
180
+ // so the resulting set is equivalent while avoiding the per-issue timeline
181
+ // query. Duplicate URLs are de-duplicated to mirror findRelatedOpenPRs, which
182
+ // collapses duplicates via its internal Map keyed by PR URL.
183
+ private resolveOpenPrsFromUrls = async (
184
+ prUrls: string[],
185
+ ): Promise<RelatedPullRequest[]> => {
186
+ const uniquePrUrls = Array.from(new Set(prUrls));
187
+ const resolvedPrs: RelatedPullRequest[] = [];
188
+ for (const prUrl of uniquePrUrls) {
189
+ const pr = await this.issueRepository.getOpenPullRequest(prUrl);
190
+ if (pr !== null) {
191
+ resolvedPrs.push(pr);
192
+ }
193
+ }
194
+ return resolvedPrs;
195
+ };
196
+
163
197
  private extractChangeTargetMustPaths = (labels: string[]): string[] => {
164
198
  const prefix = 'change-target-must:';
165
199
  const paths: string[] = [];
@@ -93,7 +93,20 @@ const createMockPullRequest = (overrides: Partial<Issue> = {}): Issue =>
93
93
  ...overrides,
94
94
  });
95
95
 
96
- const createReadyPr = (url = 'https://github.com/user/repo/pull/1') => ({
96
+ type RelatedPrLike = {
97
+ url: string;
98
+ isConflicted: boolean;
99
+ isPassedAllCiJob: boolean;
100
+ isCiStateSuccess: boolean;
101
+ isResolvedAllReviewComments: boolean;
102
+ isBranchOutOfDate: boolean;
103
+ missingRequiredCheckNames: string[];
104
+ isDraft?: boolean;
105
+ };
106
+
107
+ const createReadyPr = (
108
+ url = 'https://github.com/user/repo/pull/1',
109
+ ): RelatedPrLike => ({
97
110
  url,
98
111
  isConflicted: false,
99
112
  isPassedAllCiJob: true,
@@ -103,6 +116,37 @@ const createReadyPr = (url = 'https://github.com/user/repo/pull/1') => ({
103
116
  missingRequiredCheckNames: [],
104
117
  });
105
118
 
119
+ // Builds the in-memory linkage the production code now relies on instead of the
120
+ // per-issue findRelatedOpenPRs timeline query: each related pull request becomes
121
+ // an open PR project item whose closingIssueReferenceUrls points at the AQC
122
+ // issue, and getOpenPullRequest is routed by URL to return that PR's status.
123
+ // This proves the review-readiness outcome is identical to the previous
124
+ // findRelatedOpenPRs-based path while removing the per-issue timeline call.
125
+ const linkRelatedOpenPrsToIssue = (
126
+ mockIssueRepository: {
127
+ getAllIssues: jest.Mock;
128
+ getOpenPullRequest: jest.Mock;
129
+ },
130
+ issue: Issue,
131
+ relatedPrs: RelatedPrLike[],
132
+ ): void => {
133
+ const prItems = relatedPrs.map((pr, index) =>
134
+ createMockPullRequest({
135
+ url: pr.url,
136
+ number: 1000 + index,
137
+ closingIssueReferenceUrls: [issue.url],
138
+ }),
139
+ );
140
+ mockIssueRepository.getAllIssues.mockResolvedValue({
141
+ issues: [issue, ...prItems],
142
+ cacheUsed: false,
143
+ });
144
+ const prByUrl = new Map(relatedPrs.map((pr) => [pr.url, pr]));
145
+ mockIssueRepository.getOpenPullRequest.mockImplementation((prUrl: string) =>
146
+ Promise.resolve(prByUrl.get(prUrl) ?? null),
147
+ );
148
+ };
149
+
106
150
  describe('RevertNotReadyReviewQueueIssueUseCase', () => {
107
151
  let mockProjectRepository: {
108
152
  findProjectIdByUrl: jest.Mock;
@@ -206,7 +250,6 @@ describe('RevertNotReadyReviewQueueIssueUseCase', () => {
206
250
  issues: [issue],
207
251
  cacheUsed: false,
208
252
  });
209
- mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([]);
210
253
 
211
254
  await useCase.run({
212
255
  projectUrl: 'https://github.com/users/user/projects/1',
@@ -238,7 +281,6 @@ describe('RevertNotReadyReviewQueueIssueUseCase', () => {
238
281
  issues: [issue],
239
282
  cacheUsed: false,
240
283
  });
241
- mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([]);
242
284
 
243
285
  await useCase.run({
244
286
  projectUrl: 'https://github.com/users/user/projects/1',
@@ -260,7 +302,6 @@ describe('RevertNotReadyReviewQueueIssueUseCase', () => {
260
302
  issues: [issue],
261
303
  cacheUsed: false,
262
304
  });
263
- mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([]);
264
305
 
265
306
  await useCase.run({
266
307
  projectUrl: 'https://github.com/users/user/projects/1',
@@ -282,7 +323,6 @@ describe('RevertNotReadyReviewQueueIssueUseCase', () => {
282
323
  issues: [issue],
283
324
  cacheUsed: false,
284
325
  });
285
- mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([]);
286
326
 
287
327
  await useCase.run({
288
328
  projectUrl: 'https://github.com/users/user/projects/1',
@@ -310,7 +350,6 @@ describe('RevertNotReadyReviewQueueIssueUseCase', () => {
310
350
  issues: [issue],
311
351
  cacheUsed: false,
312
352
  });
313
- mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([]);
314
353
 
315
354
  await useCase.run({
316
355
  projectUrl: 'https://github.com/users/user/projects/1',
@@ -330,13 +369,7 @@ describe('RevertNotReadyReviewQueueIssueUseCase', () => {
330
369
  const issue = createMockIssue({
331
370
  status: 'Awaiting Quality Check',
332
371
  });
333
- mockIssueRepository.getAllIssues.mockResolvedValue({
334
- issues: [issue],
335
- cacheUsed: false,
336
- });
337
- mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([
338
- createReadyPr(),
339
- ]);
372
+ linkRelatedOpenPrsToIssue(mockIssueRepository, issue, [createReadyPr()]);
340
373
 
341
374
  await useCase.run({
342
375
  projectUrl: 'https://github.com/users/user/projects/1',
@@ -352,11 +385,7 @@ describe('RevertNotReadyReviewQueueIssueUseCase', () => {
352
385
  const issue = createMockIssue({
353
386
  status: 'Awaiting Quality Check',
354
387
  });
355
- mockIssueRepository.getAllIssues.mockResolvedValue({
356
- issues: [issue],
357
- cacheUsed: false,
358
- });
359
- mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([
388
+ linkRelatedOpenPrsToIssue(mockIssueRepository, issue, [
360
389
  { ...createReadyPr(), isConflicted: true },
361
390
  ]);
362
391
 
@@ -385,11 +414,7 @@ describe('RevertNotReadyReviewQueueIssueUseCase', () => {
385
414
  const issue = createMockIssue({
386
415
  status: 'Awaiting Quality Check',
387
416
  });
388
- mockIssueRepository.getAllIssues.mockResolvedValue({
389
- issues: [issue],
390
- cacheUsed: false,
391
- });
392
- mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([
417
+ linkRelatedOpenPrsToIssue(mockIssueRepository, issue, [
393
418
  {
394
419
  ...createReadyPr(),
395
420
  isPassedAllCiJob: false,
@@ -422,11 +447,7 @@ describe('RevertNotReadyReviewQueueIssueUseCase', () => {
422
447
  const issue = createMockIssue({
423
448
  status: 'Awaiting Quality Check',
424
449
  });
425
- mockIssueRepository.getAllIssues.mockResolvedValue({
426
- issues: [issue],
427
- cacheUsed: false,
428
- });
429
- mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([
450
+ linkRelatedOpenPrsToIssue(mockIssueRepository, issue, [
430
451
  { ...createReadyPr(), isResolvedAllReviewComments: false },
431
452
  ]);
432
453
 
@@ -455,11 +476,7 @@ describe('RevertNotReadyReviewQueueIssueUseCase', () => {
455
476
  const issue = createMockIssue({
456
477
  status: 'Awaiting Quality Check',
457
478
  });
458
- mockIssueRepository.getAllIssues.mockResolvedValue({
459
- issues: [issue],
460
- cacheUsed: false,
461
- });
462
- mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([
479
+ linkRelatedOpenPrsToIssue(mockIssueRepository, issue, [
463
480
  { ...createReadyPr(), isDraft: true },
464
481
  ]);
465
482
 
@@ -488,11 +505,7 @@ describe('RevertNotReadyReviewQueueIssueUseCase', () => {
488
505
  const issue = createMockIssue({
489
506
  status: 'Awaiting Quality Check',
490
507
  });
491
- mockIssueRepository.getAllIssues.mockResolvedValue({
492
- issues: [issue],
493
- cacheUsed: false,
494
- });
495
- mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([
508
+ linkRelatedOpenPrsToIssue(mockIssueRepository, issue, [
496
509
  createReadyPr('https://github.com/user/repo/pull/1'),
497
510
  createReadyPr('https://github.com/user/repo/pull/2'),
498
511
  ]);
@@ -522,11 +535,7 @@ describe('RevertNotReadyReviewQueueIssueUseCase', () => {
522
535
  const issue = createMockIssue({
523
536
  status: 'Awaiting Quality Check',
524
537
  });
525
- mockIssueRepository.getAllIssues.mockResolvedValue({
526
- issues: [issue],
527
- cacheUsed: false,
528
- });
529
- mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([
538
+ linkRelatedOpenPrsToIssue(mockIssueRepository, issue, [
530
539
  {
531
540
  ...createReadyPr(),
532
541
  isPassedAllCiJob: false,
@@ -556,17 +565,151 @@ describe('RevertNotReadyReviewQueueIssueUseCase', () => {
556
565
  );
557
566
  });
558
567
 
559
- describe('change-target label auto-approve', () => {
560
- const setupReadyIssue = (labels: string[]) => {
568
+ describe('in-memory related-PR derivation (no per-issue timeline call)', () => {
569
+ it('should never call findRelatedOpenPRs while sweeping Awaiting Quality Check issues', async () => {
570
+ const readyIssue = createMockIssue({
571
+ status: 'Awaiting Quality Check',
572
+ url: 'https://github.com/user/repo/issues/1',
573
+ });
574
+ const notReadyIssue = createMockIssue({
575
+ status: 'Awaiting Quality Check',
576
+ number: 2,
577
+ url: 'https://github.com/user/repo/issues/2',
578
+ });
579
+ const readyPrItem = createMockPullRequest({
580
+ status: 'In Progress',
581
+ url: 'https://github.com/user/repo/pull/100',
582
+ number: 100,
583
+ closingIssueReferenceUrls: ['https://github.com/user/repo/issues/1'],
584
+ });
585
+ const conflictedPrItem = createMockPullRequest({
586
+ status: 'In Progress',
587
+ url: 'https://github.com/user/repo/pull/200',
588
+ number: 200,
589
+ closingIssueReferenceUrls: ['https://github.com/user/repo/issues/2'],
590
+ });
591
+ mockIssueRepository.getAllIssues.mockResolvedValue({
592
+ issues: [readyIssue, notReadyIssue, readyPrItem, conflictedPrItem],
593
+ cacheUsed: false,
594
+ });
595
+ mockIssueRepository.getOpenPullRequest.mockImplementation(
596
+ (prUrl: string) => {
597
+ if (prUrl === 'https://github.com/user/repo/pull/100') {
598
+ return Promise.resolve(createReadyPr(prUrl));
599
+ }
600
+ if (prUrl === 'https://github.com/user/repo/pull/200') {
601
+ return Promise.resolve({
602
+ ...createReadyPr(prUrl),
603
+ isConflicted: true,
604
+ });
605
+ }
606
+ return Promise.resolve(null);
607
+ },
608
+ );
609
+
610
+ await useCase.run({
611
+ projectUrl: 'https://github.com/users/user/projects/1',
612
+ allowIssueCacheMinutes: 10,
613
+ allowedIssueAuthors: ['owner'],
614
+ });
615
+
616
+ expect(mockIssueRepository.findRelatedOpenPRs).not.toHaveBeenCalled();
617
+ expect(mockIssueRepository.getOpenPullRequest).toHaveBeenCalledWith(
618
+ 'https://github.com/user/repo/pull/100',
619
+ );
620
+ expect(mockIssueRepository.getOpenPullRequest).toHaveBeenCalledWith(
621
+ 'https://github.com/user/repo/pull/200',
622
+ );
623
+ expect(mockIssueRepository.updateStatus).toHaveBeenCalledTimes(1);
624
+ expect(mockIssueRepository.updateStatus).toHaveBeenCalledWith(
625
+ mockProject,
626
+ notReadyIssue,
627
+ 'awaiting-workspace-id',
628
+ );
629
+ expect(mockIssueCommentRepository.createComment).toHaveBeenCalledWith(
630
+ notReadyIssue,
631
+ expect.stringContaining('PULL_REQUEST_CONFLICTED'),
632
+ );
633
+ });
634
+
635
+ it('should match a PR to its issue via closingIssueReferenceUrls even when the PR item is listed before the issue', async () => {
561
636
  const issue = createMockIssue({
562
637
  status: 'Awaiting Quality Check',
563
- labels,
638
+ url: 'https://github.com/user/repo/issues/42',
639
+ number: 42,
640
+ });
641
+ const prItem = createMockPullRequest({
642
+ status: 'In Progress',
643
+ url: 'https://github.com/user/repo/pull/77',
644
+ number: 77,
645
+ closingIssueReferenceUrls: ['https://github.com/user/repo/issues/42'],
646
+ });
647
+ mockIssueRepository.getAllIssues.mockResolvedValue({
648
+ issues: [prItem, issue],
649
+ cacheUsed: false,
650
+ });
651
+ mockIssueRepository.getOpenPullRequest.mockResolvedValue(
652
+ createReadyPr('https://github.com/user/repo/pull/77'),
653
+ );
654
+
655
+ await useCase.run({
656
+ projectUrl: 'https://github.com/users/user/projects/1',
657
+ allowIssueCacheMinutes: 10,
658
+ allowedIssueAuthors: ['owner'],
659
+ });
660
+
661
+ expect(mockIssueRepository.getOpenPullRequest).toHaveBeenCalledWith(
662
+ 'https://github.com/user/repo/pull/77',
663
+ );
664
+ expect(mockIssueRepository.updateStatus).not.toHaveBeenCalled();
665
+ expect(mockIssueCommentRepository.createComment).not.toHaveBeenCalled();
666
+ });
667
+
668
+ it('should not match a closed PR even when its closingIssueReferenceUrls points at the issue', async () => {
669
+ const issue = createMockIssue({
670
+ status: 'Awaiting Quality Check',
671
+ url: 'https://github.com/user/repo/issues/9',
672
+ number: 9,
673
+ });
674
+ const closedPrItem = createMockPullRequest({
675
+ status: 'In Progress',
676
+ url: 'https://github.com/user/repo/pull/9',
677
+ number: 9,
678
+ isClosed: true,
679
+ state: 'CLOSED',
680
+ closingIssueReferenceUrls: ['https://github.com/user/repo/issues/9'],
564
681
  });
565
682
  mockIssueRepository.getAllIssues.mockResolvedValue({
566
- issues: [issue],
683
+ issues: [issue, closedPrItem],
567
684
  cacheUsed: false,
568
685
  });
569
- mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([
686
+
687
+ await useCase.run({
688
+ projectUrl: 'https://github.com/users/user/projects/1',
689
+ allowIssueCacheMinutes: 10,
690
+ allowedIssueAuthors: ['owner'],
691
+ });
692
+
693
+ expect(mockIssueRepository.getOpenPullRequest).not.toHaveBeenCalled();
694
+ expect(mockIssueRepository.updateStatus).toHaveBeenCalledWith(
695
+ mockProject,
696
+ issue,
697
+ 'awaiting-workspace-id',
698
+ );
699
+ expect(mockIssueCommentRepository.createComment).toHaveBeenCalledWith(
700
+ issue,
701
+ expect.stringContaining('PULL_REQUEST_NOT_FOUND'),
702
+ );
703
+ });
704
+ });
705
+
706
+ describe('change-target label auto-approve', () => {
707
+ const setupReadyIssue = (labels: string[]) => {
708
+ const issue = createMockIssue({
709
+ status: 'Awaiting Quality Check',
710
+ labels,
711
+ });
712
+ linkRelatedOpenPrsToIssue(mockIssueRepository, issue, [
570
713
  createReadyPr(),
571
714
  ]);
572
715
  return issue;
@@ -699,11 +842,7 @@ describe('RevertNotReadyReviewQueueIssueUseCase', () => {
699
842
  status: 'Awaiting Quality Check',
700
843
  labels: ['change-target:src/domain'],
701
844
  });
702
- mockIssueRepository.getAllIssues.mockResolvedValue({
703
- issues: [issue],
704
- cacheUsed: false,
705
- });
706
- mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([]);
845
+ linkRelatedOpenPrsToIssue(mockIssueRepository, issue, []);
707
846
 
708
847
  await runCycle();
709
848
 
@@ -723,11 +862,7 @@ describe('RevertNotReadyReviewQueueIssueUseCase', () => {
723
862
  status: 'Awaiting Quality Check',
724
863
  labels: ['change-target:src/domain'],
725
864
  });
726
- mockIssueRepository.getAllIssues.mockResolvedValue({
727
- issues: [issue],
728
- cacheUsed: false,
729
- });
730
- mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([
865
+ linkRelatedOpenPrsToIssue(mockIssueRepository, issue, [
731
866
  { ...createReadyPr(), isConflicted: true },
732
867
  ]);
733
868
 
@@ -749,11 +884,7 @@ describe('RevertNotReadyReviewQueueIssueUseCase', () => {
749
884
  status: 'Awaiting Quality Check',
750
885
  labels: ['llm-agent', 'change-target:src/domain'],
751
886
  });
752
- mockIssueRepository.getAllIssues.mockResolvedValue({
753
- issues: [issue],
754
- cacheUsed: false,
755
- });
756
- mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([
887
+ linkRelatedOpenPrsToIssue(mockIssueRepository, issue, [
757
888
  createReadyPr(),
758
889
  ]);
759
890
 
@@ -1,3 +1,4 @@
1
+ import { Issue } from '../entities/Issue';
1
2
  import { IssueRepository } from './adapter-interfaces/IssueRepository';
2
3
  import { ProjectRepository } from './adapter-interfaces/ProjectRepository';
3
4
  import { IssueCommentRepository } from './adapter-interfaces/IssueCommentRepository';
@@ -90,6 +91,9 @@ export class RevertNotReadyReviewQueueIssueUseCase {
90
91
  (issue) => issue.status === AWAITING_QUALITY_CHECK_STATUS_NAME,
91
92
  );
92
93
 
94
+ const relatedOpenPrUrlsByIssueUrl =
95
+ this.buildRelatedOpenPrUrlsByIssueUrl(issues);
96
+
93
97
  for (const issue of awaitingQualityCheckIssues) {
94
98
  const hasLlmAgentLabel = issue.labels.some(
95
99
  (l) => l === 'llm-agent' || l.startsWith('llm-agent:'),
@@ -108,6 +112,9 @@ export class RevertNotReadyReviewQueueIssueUseCase {
108
112
  await this.issueRejectionEvaluator.evaluate(
109
113
  issue,
110
114
  params.labelsAsLlmAgentName ?? [],
115
+ {
116
+ relatedOpenPrUrls: relatedOpenPrUrlsByIssueUrl.get(issue.url) ?? [],
117
+ },
111
118
  );
112
119
  if (rejections.length > 0) {
113
120
  await this.issueRepository.updateStatus(
@@ -175,4 +182,34 @@ export class RevertNotReadyReviewQueueIssueUseCase {
175
182
  }
176
183
  }
177
184
  };
185
+
186
+ // Derives, for each issue, the set of open pull request URLs that reference it
187
+ // via a closing keyword. The linkage is taken from each open PR item's
188
+ // closingIssueReferenceUrls (populated in bulk by fetchProjectItems), the same
189
+ // in-memory derivation SetDependedIssueUrlForOpenTaskPRsUseCase uses. This
190
+ // replaces the per-issue findRelatedOpenPRs timeline query in the
191
+ // review-readiness sweep with a single in-memory pass over the bulk items.
192
+ private buildRelatedOpenPrUrlsByIssueUrl = (
193
+ issues: Issue[],
194
+ ): Map<string, string[]> => {
195
+ const openPrUrlsByIssueUrl = new Map<string, Set<string>>();
196
+ for (const issue of issues) {
197
+ if (!issue.isPr || issue.isClosed) {
198
+ continue;
199
+ }
200
+ for (const referencedIssueUrl of issue.closingIssueReferenceUrls) {
201
+ const existing = openPrUrlsByIssueUrl.get(referencedIssueUrl);
202
+ if (existing) {
203
+ existing.add(issue.url);
204
+ } else {
205
+ openPrUrlsByIssueUrl.set(referencedIssueUrl, new Set([issue.url]));
206
+ }
207
+ }
208
+ }
209
+ const result = new Map<string, string[]>();
210
+ for (const [issueUrl, prUrls] of openPrUrlsByIssueUrl) {
211
+ result.set(issueUrl, Array.from(prUrls));
212
+ }
213
+ return result;
214
+ };
178
215
  }
@@ -7,6 +7,9 @@ export type PrRejectionResult = {
7
7
  }[];
8
8
  approvedPrUrl: string | null;
9
9
  };
10
+ export type EvaluateOptions = {
11
+ relatedOpenPrUrls?: string[] | null;
12
+ };
10
13
  export declare class IssueRejectionEvaluator {
11
14
  private readonly issueRepository;
12
15
  constructor(issueRepository: Pick<IssueRepository, 'findRelatedOpenPRs' | 'getOpenPullRequest' | 'getPullRequestChangedFilePaths' | 'requestChangesWithInlineComment'>);
@@ -14,8 +17,9 @@ export declare class IssueRejectionEvaluator {
14
17
  url: string;
15
18
  labels: string[];
16
19
  isPr: boolean;
17
- }, labelsAsLlmAgentName?: string[]) => Promise<PrRejectionResult>;
20
+ }, labelsAsLlmAgentName?: string[], options?: EvaluateOptions) => Promise<PrRejectionResult>;
18
21
  private resolveOpenPrsForPrItem;
22
+ private resolveOpenPrsFromUrls;
19
23
  private extractChangeTargetMustPaths;
20
24
  private isFilePathUnderPath;
21
25
  }
@@ -1 +1 @@
1
- {"version":3,"file":"IssueRejectionEvaluator.d.ts","sourceRoot":"","sources":["../../../src/domain/usecases/IssueRejectionEvaluator.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,eAAe,EAEhB,MAAM,sCAAsC,CAAC;AAE9C,MAAM,MAAM,oBAAoB,GAC5B,wBAAwB,GACxB,8BAA8B,GAC9B,uBAAuB,GACvB,yBAAyB,GACzB,kCAAkC,GAClC,+BAA+B,GAC/B,iCAAiC,GACjC,qCAAqC,CAAC;AAE1C,MAAM,MAAM,iBAAiB,GAAG;IAC9B,UAAU,EAAE;QAAE,IAAI,EAAE,oBAAoB,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC7D,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B,CAAC;AAEF,qBAAa,uBAAuB;IAEhC,OAAO,CAAC,QAAQ,CAAC,eAAe;gBAAf,eAAe,EAAE,IAAI,CACpC,eAAe,EACb,oBAAoB,GACpB,oBAAoB,GACpB,gCAAgC,GAChC,iCAAiC,CACpC;IAGH,QAAQ,GACN,OAAO;QACL,GAAG,EAAE,MAAM,CAAC;QACZ,MAAM,EAAE,MAAM,EAAE,CAAC;QACjB,IAAI,EAAE,OAAO,CAAC;KACf,EACD,uBAAsB,MAAM,EAAO,KAClC,OAAO,CAAC,iBAAiB,CAAC,CAgH3B;IAEF,OAAO,CAAC,uBAAuB,CAQ7B;IAEF,OAAO,CAAC,4BAA4B,CAYlC;IAEF,OAAO,CAAC,mBAAmB,CAIwC;CACpE"}
1
+ {"version":3,"file":"IssueRejectionEvaluator.d.ts","sourceRoot":"","sources":["../../../src/domain/usecases/IssueRejectionEvaluator.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,eAAe,EAEhB,MAAM,sCAAsC,CAAC;AAE9C,MAAM,MAAM,oBAAoB,GAC5B,wBAAwB,GACxB,8BAA8B,GAC9B,uBAAuB,GACvB,yBAAyB,GACzB,kCAAkC,GAClC,+BAA+B,GAC/B,iCAAiC,GACjC,qCAAqC,CAAC;AAE1C,MAAM,MAAM,iBAAiB,GAAG;IAC9B,UAAU,EAAE;QAAE,IAAI,EAAE,oBAAoB,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC7D,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAQ5B,iBAAiB,CAAC,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;CACrC,CAAC;AAEF,qBAAa,uBAAuB;IAEhC,OAAO,CAAC,QAAQ,CAAC,eAAe;gBAAf,eAAe,EAAE,IAAI,CACpC,eAAe,EACb,oBAAoB,GACpB,oBAAoB,GACpB,gCAAgC,GAChC,iCAAiC,CACpC;IAGH,QAAQ,GACN,OAAO;QACL,GAAG,EAAE,MAAM,CAAC;QACZ,MAAM,EAAE,MAAM,EAAE,CAAC;QACjB,IAAI,EAAE,OAAO,CAAC;KACf,EACD,uBAAsB,MAAM,EAAO,EACnC,UAAS,eAAoB,KAC5B,OAAO,CAAC,iBAAiB,CAAC,CAkH3B;IAEF,OAAO,CAAC,uBAAuB,CAQ7B;IAQF,OAAO,CAAC,sBAAsB,CAY5B;IAEF,OAAO,CAAC,4BAA4B,CAYlC;IAEF,OAAO,CAAC,mBAAmB,CAIwC;CACpE"}
@@ -15,5 +15,6 @@ export declare class RevertNotReadyReviewQueueIssueUseCase {
15
15
  changeTargetPathAliases?: Record<string, string> | null;
16
16
  allowedIssueAuthors?: string[] | null;
17
17
  }) => Promise<void>;
18
+ private buildRelatedOpenPrUrlsByIssueUrl;
18
19
  }
19
20
  //# sourceMappingURL=RevertNotReadyReviewQueueIssueUseCase.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"RevertNotReadyReviewQueueIssueUseCase.d.ts","sourceRoot":"","sources":["../../../src/domain/usecases/RevertNotReadyReviewQueueIssueUseCase.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,sCAAsC,CAAC;AACvE,OAAO,EAAE,iBAAiB,EAAE,MAAM,wCAAwC,CAAC;AAC3E,OAAO,EAAE,sBAAsB,EAAE,MAAM,6CAA6C,CAAC;AAsBrF,qBAAa,qCAAqC;IAK9C,OAAO,CAAC,QAAQ,CAAC,iBAAiB;IAIlC,OAAO,CAAC,QAAQ,CAAC,eAAe;IAWhC,OAAO,CAAC,QAAQ,CAAC,sBAAsB;IAnBzC,OAAO,CAAC,QAAQ,CAAC,uBAAuB,CAA0B;IAClE,OAAO,CAAC,QAAQ,CAAC,+BAA+B,CAAkC;gBAG/D,iBAAiB,EAAE,IAAI,CACtC,iBAAiB,EACjB,oBAAoB,GAAG,YAAY,CACpC,EACgB,eAAe,EAAE,IAAI,CACpC,eAAe,EACb,cAAc,GACd,cAAc,GACd,aAAa,GACb,oBAAoB,GACpB,oBAAoB,GACpB,gCAAgC,GAChC,oBAAoB,GACpB,iCAAiC,CACpC,EACgB,sBAAsB,EAAE,IAAI,CAC3C,sBAAsB,EACtB,eAAe,CAChB;IAQH,GAAG,GAAU,QAAQ;QACnB,UAAU,EAAE,MAAM,CAAC;QACnB,sBAAsB,EAAE,MAAM,CAAC;QAC/B,oBAAoB,CAAC,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;QACvC,uBAAuB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAAC;QACxD,mBAAmB,CAAC,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;KACvC,KAAG,OAAO,CAAC,IAAI,CAAC,CAmHf;CACH"}
1
+ {"version":3,"file":"RevertNotReadyReviewQueueIssueUseCase.d.ts","sourceRoot":"","sources":["../../../src/domain/usecases/RevertNotReadyReviewQueueIssueUseCase.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,sCAAsC,CAAC;AACvE,OAAO,EAAE,iBAAiB,EAAE,MAAM,wCAAwC,CAAC;AAC3E,OAAO,EAAE,sBAAsB,EAAE,MAAM,6CAA6C,CAAC;AAsBrF,qBAAa,qCAAqC;IAK9C,OAAO,CAAC,QAAQ,CAAC,iBAAiB;IAIlC,OAAO,CAAC,QAAQ,CAAC,eAAe;IAWhC,OAAO,CAAC,QAAQ,CAAC,sBAAsB;IAnBzC,OAAO,CAAC,QAAQ,CAAC,uBAAuB,CAA0B;IAClE,OAAO,CAAC,QAAQ,CAAC,+BAA+B,CAAkC;gBAG/D,iBAAiB,EAAE,IAAI,CACtC,iBAAiB,EACjB,oBAAoB,GAAG,YAAY,CACpC,EACgB,eAAe,EAAE,IAAI,CACpC,eAAe,EACb,cAAc,GACd,cAAc,GACd,aAAa,GACb,oBAAoB,GACpB,oBAAoB,GACpB,gCAAgC,GAChC,oBAAoB,GACpB,iCAAiC,CACpC,EACgB,sBAAsB,EAAE,IAAI,CAC3C,sBAAsB,EACtB,eAAe,CAChB;IAQH,GAAG,GAAU,QAAQ;QACnB,UAAU,EAAE,MAAM,CAAC;QACnB,sBAAsB,EAAE,MAAM,CAAC;QAC/B,oBAAoB,CAAC,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;QACvC,uBAAuB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAAC;QACxD,mBAAmB,CAAC,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;KACvC,KAAG,OAAO,CAAC,IAAI,CAAC,CAyHf;IAQF,OAAO,CAAC,gCAAgC,CAsBtC;CACH"}