github-issue-tower-defence-management 1.179.0 → 1.180.0

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 (70) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/bin/adapter/entry-points/console/consoleOperationApi.js +42 -1
  3. package/bin/adapter/entry-points/console/consoleOperationApi.js.map +1 -1
  4. package/bin/adapter/entry-points/console/ui-dist/assets/index-I_2Yu8fM.js +86 -0
  5. package/bin/adapter/entry-points/console/ui-dist/index.html +1 -1
  6. package/bin/adapter/entry-points/console/webServer.js +6 -0
  7. package/bin/adapter/entry-points/console/webServer.js.map +1 -1
  8. package/bin/adapter/repositories/SystemDateRepository.js +10 -10
  9. package/bin/adapter/repositories/SystemDateRepository.js.map +1 -1
  10. package/bin/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.js +23 -4
  11. package/bin/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.js.map +1 -1
  12. package/bin/domain/usecases/AnalyzeProblemByIssueUseCase.js +1 -1
  13. package/bin/domain/usecases/AnalyzeProblemByIssueUseCase.js.map +1 -1
  14. package/bin/domain/usecases/AnalyzeStoriesUseCase.js +1 -1
  15. package/bin/domain/usecases/AnalyzeStoriesUseCase.js.map +1 -1
  16. package/bin/domain/usecases/ClearPastNextActionDateHourUseCase.js +1 -1
  17. package/bin/domain/usecases/ClearPastNextActionDateHourUseCase.js.map +1 -1
  18. package/bin/domain/usecases/CreateEstimationIssueUseCase.js +3 -3
  19. package/bin/domain/usecases/CreateEstimationIssueUseCase.js.map +1 -1
  20. package/bin/domain/usecases/HandleScheduledEventUseCase.js +6 -6
  21. package/bin/domain/usecases/HandleScheduledEventUseCase.js.map +1 -1
  22. package/bin/domain/usecases/IssueRejectionEvaluator.js +3 -1
  23. package/bin/domain/usecases/IssueRejectionEvaluator.js.map +1 -1
  24. package/bin/domain/usecases/StartPreparationUseCase.js +1 -1
  25. package/bin/domain/usecases/StartPreparationUseCase.js.map +1 -1
  26. package/bin/domain/usecases/issueReactivationTriggerIsPending.js +2 -2
  27. package/bin/domain/usecases/issueReactivationTriggerIsPending.js.map +1 -1
  28. package/package.json +1 -1
  29. package/src/adapter/entry-points/console/consoleOperationApi.test.ts +230 -0
  30. package/src/adapter/entry-points/console/consoleOperationApi.ts +48 -1
  31. package/src/adapter/entry-points/console/ui/e2e/consoleScenario.spec.ts +36 -0
  32. package/src/adapter/entry-points/console/ui/e2e/consoleTestHarness.ts +25 -1
  33. package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleStoryReorderPanel.stories.tsx +30 -0
  34. package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleStoryReorderPanel.test.tsx +111 -0
  35. package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleStoryReorderPanel.tsx +91 -0
  36. package/src/adapter/entry-points/console/ui/src/features/console/lib/consoleApi.ts +21 -0
  37. package/src/adapter/entry-points/console/ui/src/features/console/logic/relativeTime.test.ts +24 -2
  38. package/src/adapter/entry-points/console/ui/src/features/console/logic/relativeTime.ts +3 -1
  39. package/src/adapter/entry-points/console/ui/src/features/console/pages/ConsolePage.tsx +60 -11
  40. package/src/adapter/entry-points/console/ui-dist/assets/index-I_2Yu8fM.js +86 -0
  41. package/src/adapter/entry-points/console/ui-dist/index.html +1 -1
  42. package/src/adapter/entry-points/console/webServer.ts +11 -0
  43. package/src/adapter/repositories/SystemDateRepository.test.ts +60 -0
  44. package/src/adapter/repositories/SystemDateRepository.ts +10 -10
  45. package/src/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.test.ts +268 -2
  46. package/src/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.ts +33 -4
  47. package/src/domain/usecases/AnalyzeProblemByIssueUseCase.ts +1 -1
  48. package/src/domain/usecases/AnalyzeStoriesUseCase.test.ts +80 -0
  49. package/src/domain/usecases/AnalyzeStoriesUseCase.ts +1 -1
  50. package/src/domain/usecases/ClearPastNextActionDateHourUseCase.ts +6 -4
  51. package/src/domain/usecases/CreateEstimationIssueUseCase.test.ts +225 -0
  52. package/src/domain/usecases/CreateEstimationIssueUseCase.ts +3 -3
  53. package/src/domain/usecases/HandleScheduledEventUseCase.test.ts +107 -0
  54. package/src/domain/usecases/HandleScheduledEventUseCase.ts +7 -7
  55. package/src/domain/usecases/IssueRejectionEvaluator.test.ts +83 -0
  56. package/src/domain/usecases/IssueRejectionEvaluator.ts +3 -1
  57. package/src/domain/usecases/StartPreparationUseCase.ts +5 -3
  58. package/src/domain/usecases/issueReactivationTriggerIsPending.test.ts +33 -33
  59. package/src/domain/usecases/issueReactivationTriggerIsPending.ts +6 -4
  60. package/types/adapter/entry-points/console/consoleOperationApi.d.ts +3 -1
  61. package/types/adapter/entry-points/console/consoleOperationApi.d.ts.map +1 -1
  62. package/types/adapter/entry-points/console/webServer.d.ts +2 -0
  63. package/types/adapter/entry-points/console/webServer.d.ts.map +1 -1
  64. package/types/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.d.ts.map +1 -1
  65. package/types/domain/usecases/ClearPastNextActionDateHourUseCase.d.ts.map +1 -1
  66. package/types/domain/usecases/IssueRejectionEvaluator.d.ts.map +1 -1
  67. package/types/domain/usecases/StartPreparationUseCase.d.ts.map +1 -1
  68. package/types/domain/usecases/issueReactivationTriggerIsPending.d.ts.map +1 -1
  69. package/bin/adapter/entry-points/console/ui-dist/assets/index-KCmqh781.js +0 -86
  70. package/src/adapter/entry-points/console/ui-dist/assets/index-KCmqh781.js +0 -86
@@ -0,0 +1,225 @@
1
+ import { mock } from 'jest-mock-extended';
2
+ import { CreateEstimationIssueUseCase } from './CreateEstimationIssueUseCase';
3
+ import { IssueRepository } from './adapter-interfaces/IssueRepository';
4
+ import { DateRepository } from './adapter-interfaces/DateRepository';
5
+ import { Project } from '../entities/Project';
6
+ import { StoryObject } from '../entities/StoryObjectMap';
7
+ import { Issue } from '../entities/Issue';
8
+
9
+ describe('CreateEstimationIssueUseCase', () => {
10
+ const mockIssueRepository = mock<IssueRepository>();
11
+ const mockDateRepository = mock<DateRepository>();
12
+
13
+ let useCase: CreateEstimationIssueUseCase;
14
+
15
+ const projectWithStory: Project = {
16
+ ...mock<Project>(),
17
+ story: {
18
+ name: 'Story',
19
+ fieldId: 'story-field',
20
+ databaseId: 1,
21
+ stories: [],
22
+ workflowManagementStory: { id: 'wms-id', name: 'workflow management' },
23
+ },
24
+ remainingEstimationMinutes: null,
25
+ completionDate50PercentConfidence: null,
26
+ };
27
+
28
+ const commonInput = {
29
+ project: projectWithStory,
30
+ issues: [],
31
+ cacheUsed: false,
32
+ manager: 'manager-user',
33
+ org: 'org',
34
+ repo: 'repo',
35
+ urlOfStoryView: 'https://github.com/org/repo',
36
+ storyObjectMap: new Map<string, StoryObject>(),
37
+ };
38
+
39
+ beforeEach(() => {
40
+ jest.clearAllMocks();
41
+ useCase = new CreateEstimationIssueUseCase(
42
+ mockIssueRepository,
43
+ mockDateRepository,
44
+ );
45
+ });
46
+
47
+ describe('run — UTC 07:00 guard', () => {
48
+ it('returns early when no targetDate is at UTC 07:00', async () => {
49
+ const nonMatchingDate = new Date(Date.UTC(2026, 0, 15, 6, 0, 0));
50
+
51
+ await useCase.run({ ...commonInput, targetDates: [nonMatchingDate] });
52
+
53
+ expect(mockIssueRepository.createNewIssue).not.toHaveBeenCalled();
54
+ });
55
+
56
+ it('returns early when targetDates is empty', async () => {
57
+ await useCase.run({ ...commonInput, targetDates: [] });
58
+
59
+ expect(mockIssueRepository.createNewIssue).not.toHaveBeenCalled();
60
+ });
61
+
62
+ it('returns early when project has no story field', async () => {
63
+ const projectWithoutStory: Project = { ...projectWithStory, story: null };
64
+
65
+ await useCase.run({
66
+ ...commonInput,
67
+ project: projectWithoutStory,
68
+ targetDates: [new Date(Date.UTC(2026, 0, 15, 7, 0, 0))],
69
+ });
70
+
71
+ expect(mockIssueRepository.createNewIssue).not.toHaveBeenCalled();
72
+ });
73
+ });
74
+
75
+ describe('run — weekend skip', () => {
76
+ const projectWithNonRegularStory: Project = {
77
+ ...projectWithStory,
78
+ story: {
79
+ name: 'Story',
80
+ fieldId: 'story-field',
81
+ databaseId: 1,
82
+ stories: [
83
+ {
84
+ id: 'story-1',
85
+ name: 'Feature Story',
86
+ color: 'BLUE',
87
+ description: '',
88
+ },
89
+ ],
90
+ workflowManagementStory: { id: 'wms-id', name: 'workflow management' },
91
+ },
92
+ };
93
+
94
+ it('returns early when the last targetDate falls on Saturday UTC', async () => {
95
+ const saturdayAt07h = new Date(Date.UTC(2026, 0, 17, 7, 0, 0));
96
+ expect(saturdayAt07h.getUTCDay()).toBe(6);
97
+
98
+ await useCase.run({
99
+ ...commonInput,
100
+ project: projectWithNonRegularStory,
101
+ targetDates: [saturdayAt07h],
102
+ });
103
+
104
+ expect(mockIssueRepository.createNewIssue).not.toHaveBeenCalled();
105
+ });
106
+
107
+ it('returns early when the last targetDate falls on Sunday UTC', async () => {
108
+ const sundayAt07h = new Date(Date.UTC(2026, 0, 18, 7, 0, 0));
109
+ expect(sundayAt07h.getUTCDay()).toBe(0);
110
+
111
+ await useCase.run({
112
+ ...commonInput,
113
+ project: projectWithNonRegularStory,
114
+ targetDates: [sundayAt07h],
115
+ });
116
+
117
+ expect(mockIssueRepository.createNewIssue).not.toHaveBeenCalled();
118
+ });
119
+
120
+ it('proceeds past the weekend guard on a weekday UTC 07:00', async () => {
121
+ const thursdayAt07h = new Date(Date.UTC(2026, 0, 15, 7, 0, 0));
122
+ expect(thursdayAt07h.getUTCDay()).toBe(4);
123
+
124
+ await expect(
125
+ useCase.run({
126
+ ...commonInput,
127
+ project: projectWithNonRegularStory,
128
+ targetDates: [thursdayAt07h],
129
+ }),
130
+ ).rejects.toThrow('Story issue not found: Feature Story');
131
+ });
132
+ });
133
+
134
+ describe('run — completionDate50PercentConfidence Monday UTC reset', () => {
135
+ const mondayAt07hUTC = new Date(Date.UTC(2026, 0, 12, 7, 0, 0));
136
+
137
+ const featureStory = {
138
+ id: 'story-feat',
139
+ name: 'Feature Story',
140
+ color: 'BLUE' as const,
141
+ description: '',
142
+ };
143
+
144
+ const projectWithCompletionField: Project = {
145
+ ...mock<Project>(),
146
+ story: {
147
+ name: 'Story',
148
+ fieldId: 'story-field',
149
+ databaseId: 1,
150
+ stories: [featureStory],
151
+ workflowManagementStory: { id: 'wms-id', name: 'workflow management' },
152
+ },
153
+ remainingEstimationMinutes: null,
154
+ completionDate50PercentConfidence: {
155
+ name: 'Completion Date',
156
+ fieldId: 'completion-field',
157
+ },
158
+ };
159
+
160
+ const storyIssue: Issue = {
161
+ ...mock<Issue>(),
162
+ title: 'Feature Story',
163
+ labels: ['story', 'story:action:schedule-control'],
164
+ isClosed: false,
165
+ isPr: false,
166
+ url: 'https://github.com/org/repo/issues/1',
167
+ };
168
+
169
+ const farFutureDate = new Date(Date.UTC(2026, 5, 1, 0, 0, 0));
170
+ const issueInStory: Issue = {
171
+ ...mock<Issue>(),
172
+ title: 'Task issue',
173
+ labels: [],
174
+ isClosed: false,
175
+ isPr: false,
176
+ status: null,
177
+ estimationMinutes: null,
178
+ completionDate50PercentConfidence: farFutureDate,
179
+ assignees: ['dev-user'],
180
+ };
181
+
182
+ beforeEach(() => {
183
+ jest.useFakeTimers();
184
+ });
185
+
186
+ afterEach(() => {
187
+ jest.useRealTimers();
188
+ });
189
+
190
+ it('clears completionDate50PercentConfidence on UTC Monday even when date is far in future', async () => {
191
+ expect(mondayAt07hUTC.getUTCDay()).toBe(1);
192
+
193
+ mockDateRepository.formatDateWithDayOfWeek.mockReturnValue(
194
+ 'Mon, Jun 01, 2026',
195
+ );
196
+
197
+ const storyObjectMap = new Map<string, StoryObject>([
198
+ [
199
+ featureStory.name,
200
+ { story: featureStory, storyIssue, issues: [issueInStory] },
201
+ ],
202
+ ]);
203
+
204
+ const runPromise = useCase.run({
205
+ project: projectWithCompletionField,
206
+ issues: [storyIssue],
207
+ cacheUsed: false,
208
+ manager: 'manager-user',
209
+ org: 'org',
210
+ repo: 'repo',
211
+ urlOfStoryView: 'https://github.com/org/repo',
212
+ storyObjectMap,
213
+ targetDates: [mondayAt07hUTC],
214
+ });
215
+ await jest.runAllTimersAsync();
216
+ await runPromise;
217
+
218
+ expect(mockIssueRepository.clearProjectField).toHaveBeenCalledWith(
219
+ projectWithCompletionField,
220
+ 'completion-field',
221
+ issueInStory,
222
+ );
223
+ });
224
+ });
225
+ });
@@ -32,7 +32,7 @@ export class CreateEstimationIssueUseCase {
32
32
  !story ||
33
33
  !input.targetDates.find(
34
34
  (targetDate) =>
35
- targetDate.getHours() === 7 && targetDate.getMinutes() === 0,
35
+ targetDate.getUTCHours() === 7 && targetDate.getUTCMinutes() === 0,
36
36
  )
37
37
  ) {
38
38
  return;
@@ -41,7 +41,7 @@ export class CreateEstimationIssueUseCase {
41
41
  if (!targetDate) {
42
42
  return;
43
43
  }
44
- if (targetDate.getDay() === 0 || targetDate.getDay() === 6) {
44
+ if (targetDate.getUTCDay() === 0 || targetDate.getUTCDay() === 6) {
45
45
  return;
46
46
  }
47
47
 
@@ -91,7 +91,7 @@ export class CreateEstimationIssueUseCase {
91
91
  !!issueInStory.completionDate50PercentConfidence &&
92
92
  (issueInStory.completionDate50PercentConfidence.getTime() <
93
93
  targetDate.getTime() + 7 * 24 * 60 * 60 * 1000 ||
94
- targetDate.getDay() === 1)
94
+ targetDate.getUTCDay() === 1)
95
95
  ) {
96
96
  await this.issueRepository.createComment(
97
97
  issueInStory,
@@ -1617,4 +1617,111 @@ describe('HandleScheduledEventUseCase', () => {
1617
1617
  });
1618
1618
  });
1619
1619
  });
1620
+
1621
+ describe('storyIssues', () => {
1622
+ const useCase = new HandleScheduledEventUseCase(
1623
+ mock<ProjectRequiredFieldCreateUseCase>(),
1624
+ mock<SetupTowerDefenceProjectUseCase>(),
1625
+ mock<ActionAnnouncementUseCase>(),
1626
+ mock<SetWorkflowManagementIssueToStoryUseCase>(),
1627
+ mock<ClearPastNextActionDateHourUseCase>(),
1628
+ mock<AnalyzeProblemByIssueUseCase>(),
1629
+ mock<AnalyzeStoriesUseCase>(),
1630
+ mock<ClearDependedIssueURLUseCase>(),
1631
+ mock<SetDependedIssueUrlForOpenTaskPRsUseCase>(),
1632
+ mock<StaleTaskPullRequestCloseUseCase>(),
1633
+ mock<CreateEstimationIssueUseCase>(),
1634
+ mock<ConvertCheckboxToIssueInStoryIssueUseCase>(),
1635
+ mock<ChangeStatusByStoryColorUseCase>(),
1636
+ mock<SetNoStoryIssueToStoryUseCase>(),
1637
+ mock<CreateNewStoryByLabelUseCase>(),
1638
+ mock<AssignNoAssigneeIssueToManagerUseCase>(),
1639
+ mock<UpdateIssueStatusByLabelUseCase>(),
1640
+ mock<StartPreparationUseCase>(),
1641
+ mock<RevertOrphanedPreparationUseCase>(),
1642
+ mock<RevertNotReadyReviewQueueIssueUseCase>(),
1643
+ mock<TriagerApprovalDispatchUseCase>(),
1644
+ mock<AgentDesignationLabelAdoptUseCase>(),
1645
+ null,
1646
+ null,
1647
+ mock<QualityCheckAdvanceUseCase>(),
1648
+ mock<DateRepository>(),
1649
+ mock<SpreadsheetRepository>(),
1650
+ mock<ProjectRepository>(),
1651
+ mock<IssueRepository>(),
1652
+ );
1653
+
1654
+ const storyName = 'my story / feature';
1655
+ const baseProject: Project = {
1656
+ id: 'project-1',
1657
+ url: 'https://github.com/orgs/user/projects/1',
1658
+ databaseId: 1,
1659
+ name: 'Test Project',
1660
+ status: { name: 'Status', fieldId: 'status-field-id', statuses: [] },
1661
+ nextActionDate: null,
1662
+ nextActionHour: null,
1663
+ story: {
1664
+ name: 'Story',
1665
+ fieldId: 'story-field-id',
1666
+ databaseId: 1,
1667
+ stories: [
1668
+ { id: 'story-1', name: storyName, color: 'BLUE', description: '' },
1669
+ ],
1670
+ workflowManagementStory: { id: 'wms-1', name: 'workflow management' },
1671
+ },
1672
+ remainingEstimationMinutes: null,
1673
+ dependedIssueUrlSeparatedByComma: null,
1674
+ completionDate50PercentConfidence: null,
1675
+ agent: null,
1676
+ };
1677
+
1678
+ const baseIssue: Issue = {
1679
+ nameWithOwner: 'user/repo',
1680
+ number: 1,
1681
+ title: storyName,
1682
+ state: 'OPEN',
1683
+ status: null,
1684
+ story: null,
1685
+ nextActionDate: null,
1686
+ nextActionHour: null,
1687
+ estimationMinutes: null,
1688
+ dependedIssueUrls: [],
1689
+ completionDate50PercentConfidence: null,
1690
+ url: 'https://github.com/user/repo/issues/1',
1691
+ assignees: [],
1692
+ labels: [],
1693
+ org: 'user',
1694
+ repo: 'repo',
1695
+ body: '',
1696
+ itemId: 'item-1',
1697
+ isPr: false,
1698
+ isInProgress: false,
1699
+ isClosed: false,
1700
+ createdAt: new Date('2024-01-01T00:00:00Z'),
1701
+ author: '',
1702
+ closingIssueReferenceUrls: [],
1703
+ agent: null,
1704
+ };
1705
+
1706
+ it('should return storyIssue as null when the only matching issue is closed', async () => {
1707
+ const closedIssue: Issue = {
1708
+ ...baseIssue,
1709
+ state: 'CLOSED',
1710
+ isClosed: true,
1711
+ };
1712
+ const result = await useCase.storyIssues({
1713
+ project: baseProject,
1714
+ issues: [closedIssue],
1715
+ });
1716
+ expect(result.get(storyName)?.storyIssue).toBeNull();
1717
+ });
1718
+
1719
+ it('should return the open matching issue as storyIssue', async () => {
1720
+ const result = await useCase.storyIssues({
1721
+ project: baseProject,
1722
+ issues: [baseIssue],
1723
+ });
1724
+ expect(result.get(storyName)?.storyIssue).toBe(baseIssue);
1725
+ });
1726
+ });
1620
1727
  });
@@ -622,20 +622,20 @@ ${JSON.stringify(e)}
622
622
  const targetDateTimes: Date[] = [];
623
623
  if (from.getTime() > to.getTime()) {
624
624
  const targetDate = new Date(to);
625
- targetDate.setSeconds(0);
626
- targetDate.setMilliseconds(0);
625
+ targetDate.setUTCSeconds(0);
626
+ targetDate.setUTCMilliseconds(0);
627
627
  return [targetDate];
628
628
  }
629
629
  const targetDate = new Date(from);
630
630
  targetDate.setTime(targetDate.getTime() + 60 * 1000);
631
- targetDate.setSeconds(0);
632
- targetDate.setMilliseconds(0);
631
+ targetDate.setUTCSeconds(0);
632
+ targetDate.setUTCMilliseconds(0);
633
633
  while (
634
634
  targetDate.getTime() <= to.getTime() &&
635
635
  targetDateTimes.length < 300
636
636
  ) {
637
637
  targetDateTimes.push(new Date(targetDate));
638
- targetDate.setMinutes(targetDate.getMinutes() + 1);
638
+ targetDate.setUTCMinutes(targetDate.getUTCMinutes() + 1);
639
639
  }
640
640
  return targetDateTimes;
641
641
  };
@@ -816,8 +816,8 @@ ${JSON.stringify(e)}
816
816
  const summaryStoryIssue: StoryObjectMap = new Map();
817
817
  const targetStory = input.project.story?.stories || [];
818
818
  for (const story of targetStory) {
819
- const storyIssue = input.issues.find((issue) =>
820
- story.name.startsWith(issue.title),
819
+ const storyIssue = input.issues.find(
820
+ (issue) => story.name.startsWith(issue.title) && !issue.isClosed,
821
821
  );
822
822
  summaryStoryIssue.set(story.name, {
823
823
  story,
@@ -258,6 +258,89 @@ describe('IssueRejectionEvaluator', () => {
258
258
  expect(result.rejections[0].type).toBe('PULL_REQUEST_NOT_FOUND');
259
259
  });
260
260
 
261
+ it('should require PR evaluation and reject CI failure when issue agent is pr-reviewer', async () => {
262
+ mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([
263
+ createReadyPr('https://github.com/user/repo/pull/10', {
264
+ isPassedAllCiJob: false,
265
+ isCiStateSuccess: false,
266
+ }),
267
+ ]);
268
+
269
+ const result = await evaluator.evaluate({
270
+ url: 'https://github.com/user/repo/issues/1',
271
+ labels: [],
272
+ isPr: false,
273
+ agent: 'pr-reviewer',
274
+ });
275
+
276
+ expect(mockIssueRepository.findRelatedOpenPRs).toHaveBeenCalled();
277
+ expect(result.rejections.map((r) => r.type)).toContain(
278
+ 'ANY_CI_JOB_FAILED_OR_IN_PROGRESS',
279
+ );
280
+ });
281
+
282
+ it('should require PR evaluation and reject conflict when issue agent is pr-reviewer', async () => {
283
+ mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([
284
+ createReadyPr('https://github.com/user/repo/pull/10', {
285
+ isConflicted: true,
286
+ }),
287
+ ]);
288
+
289
+ const result = await evaluator.evaluate({
290
+ url: 'https://github.com/user/repo/issues/1',
291
+ labels: [],
292
+ isPr: false,
293
+ agent: 'pr-reviewer',
294
+ });
295
+
296
+ expect(mockIssueRepository.findRelatedOpenPRs).toHaveBeenCalled();
297
+ expect(result.rejections.map((r) => r.type)).toContain(
298
+ 'PULL_REQUEST_CONFLICTED',
299
+ );
300
+ });
301
+
302
+ it('should require PR evaluation and approve when issue agent is pr-reviewer with passing CI and no conflict', async () => {
303
+ mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([
304
+ createReadyPr('https://github.com/user/repo/pull/10'),
305
+ ]);
306
+
307
+ const result = await evaluator.evaluate({
308
+ url: 'https://github.com/user/repo/issues/1',
309
+ labels: [],
310
+ isPr: false,
311
+ agent: 'pr-reviewer',
312
+ });
313
+
314
+ expect(mockIssueRepository.findRelatedOpenPRs).toHaveBeenCalled();
315
+ expect(result.rejections).toHaveLength(0);
316
+ expect(result.approvedPrUrl).toBe('https://github.com/user/repo/pull/10');
317
+ });
318
+
319
+ it('should still require PR evaluation for pr-reviewer when custom developerAgentName is set', async () => {
320
+ mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([
321
+ createReadyPr('https://github.com/user/repo/pull/10', {
322
+ isPassedAllCiJob: false,
323
+ isCiStateSuccess: false,
324
+ }),
325
+ ]);
326
+
327
+ const result = await evaluator.evaluate(
328
+ {
329
+ url: 'https://github.com/user/repo/issues/1',
330
+ labels: [],
331
+ isPr: false,
332
+ agent: 'pr-reviewer',
333
+ },
334
+ [],
335
+ { developerAgentName: 'my-developer' },
336
+ );
337
+
338
+ expect(mockIssueRepository.findRelatedOpenPRs).toHaveBeenCalled();
339
+ expect(result.rejections.map((r) => r.type)).toContain(
340
+ 'ANY_CI_JOB_FAILED_OR_IN_PROGRESS',
341
+ );
342
+ });
343
+
261
344
  it('should not reject for draft state when issue has non-e2e category label', async () => {
262
345
  const result = await evaluator.evaluate({
263
346
  url: 'https://github.com/user/repo/issues/1',
@@ -204,7 +204,9 @@ export class IssueRejectionEvaluator {
204
204
  );
205
205
  const effectiveDeveloperAgentName = developerAgentName ?? 'developer';
206
206
  const isNonDeveloperAgent =
207
- issue.agent != null && issue.agent !== effectiveDeveloperAgentName;
207
+ issue.agent != null &&
208
+ issue.agent !== effectiveDeveloperAgentName &&
209
+ issue.agent !== 'pr-reviewer';
208
210
  const hasLabelNotRequiringPullRequest = issue.labels.some((label) =>
209
211
  labelsNotRequiringPullRequest.includes(label),
210
212
  );
@@ -453,9 +453,11 @@ export class StartPreparationUseCase {
453
453
  }
454
454
  if (issueReactivationTriggerIsPending(issue, now)) {
455
455
  const startOfTomorrow = new Date(
456
- now.getFullYear(),
457
- now.getMonth(),
458
- now.getDate() + 1,
456
+ Date.UTC(
457
+ now.getUTCFullYear(),
458
+ now.getUTCMonth(),
459
+ now.getUTCDate() + 1,
460
+ ),
459
461
  );
460
462
  if (
461
463
  issue.nextActionDate !== null &&