github-issue-tower-defence-management 2.6.1 → 2.7.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 (75) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/README.md +0 -2
  3. package/bin/adapter/entry-points/cli/index.js +0 -5
  4. package/bin/adapter/entry-points/cli/index.js.map +1 -1
  5. package/bin/adapter/entry-points/cli/projectConfig.js +0 -3
  6. package/bin/adapter/entry-points/cli/projectConfig.js.map +1 -1
  7. package/bin/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.js +1 -3
  8. package/bin/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.js.map +1 -1
  9. package/bin/domain/usecases/CheckIssueReviewReadinessUseCase.js +4 -2
  10. package/bin/domain/usecases/CheckIssueReviewReadinessUseCase.js.map +1 -1
  11. package/bin/domain/usecases/HandleScheduledEventUseCase.js +1 -6
  12. package/bin/domain/usecases/HandleScheduledEventUseCase.js.map +1 -1
  13. package/bin/domain/usecases/IssueRejectionEvaluator.js +0 -23
  14. package/bin/domain/usecases/IssueRejectionEvaluator.js.map +1 -1
  15. package/bin/domain/usecases/NotifyFinishedIssuePreparationUseCase.js +39 -69
  16. package/bin/domain/usecases/NotifyFinishedIssuePreparationUseCase.js.map +1 -1
  17. package/bin/domain/usecases/RevertNotReadyReviewQueueIssueUseCase.js +3 -2
  18. package/bin/domain/usecases/RevertNotReadyReviewQueueIssueUseCase.js.map +1 -1
  19. package/bin/domain/usecases/RevertOrphanedPreparationUseCase.js +0 -43
  20. package/bin/domain/usecases/RevertOrphanedPreparationUseCase.js.map +1 -1
  21. package/bin/domain/usecases/autoStatusCheckComments.js +1 -11
  22. package/bin/domain/usecases/autoStatusCheckComments.js.map +1 -1
  23. package/bin/domain/usecases/extractNextStepAgentFromComments.js +14 -0
  24. package/bin/domain/usecases/extractNextStepAgentFromComments.js.map +1 -0
  25. package/bin/domain/usecases/triagerAgentName.js +7 -0
  26. package/bin/domain/usecases/triagerAgentName.js.map +1 -0
  27. package/package.json +1 -1
  28. package/src/adapter/entry-points/cli/index.test.ts +0 -2
  29. package/src/adapter/entry-points/cli/index.ts +0 -9
  30. package/src/adapter/entry-points/cli/projectConfig.ts +0 -8
  31. package/src/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.ts +0 -7
  32. package/src/domain/usecases/CheckIssueReviewReadinessUseCase.test.ts +26 -4
  33. package/src/domain/usecases/CheckIssueReviewReadinessUseCase.ts +5 -3
  34. package/src/domain/usecases/HandleScheduledEventUseCase.test.ts +0 -28
  35. package/src/domain/usecases/HandleScheduledEventUseCase.ts +0 -6
  36. package/src/domain/usecases/IssueRejectionEvaluator.test.ts +0 -119
  37. package/src/domain/usecases/IssueRejectionEvaluator.ts +0 -21
  38. package/src/domain/usecases/NotifyFinishedIssuePreparationUseCase.test.ts +185 -327
  39. package/src/domain/usecases/NotifyFinishedIssuePreparationUseCase.ts +68 -138
  40. package/src/domain/usecases/RevertNotReadyReviewQueueIssueUseCase.test.ts +4 -4
  41. package/src/domain/usecases/RevertNotReadyReviewQueueIssueUseCase.ts +11 -5
  42. package/src/domain/usecases/RevertOrphanedPreparationUseCase.test.ts +0 -313
  43. package/src/domain/usecases/RevertOrphanedPreparationUseCase.ts +1 -102
  44. package/src/domain/usecases/autoStatusCheckComments.ts +0 -17
  45. package/src/domain/usecases/extractNextStepAgentFromComments.ts +13 -0
  46. package/src/domain/usecases/isHumanComment.test.ts +0 -13
  47. package/src/domain/usecases/triagerAgentName.ts +4 -0
  48. package/types/adapter/entry-points/cli/index.d.ts.map +1 -1
  49. package/types/adapter/entry-points/cli/projectConfig.d.ts +0 -1
  50. package/types/adapter/entry-points/cli/projectConfig.d.ts.map +1 -1
  51. package/types/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.d.ts.map +1 -1
  52. package/types/domain/usecases/CheckIssueReviewReadinessUseCase.d.ts.map +1 -1
  53. package/types/domain/usecases/HandleScheduledEventUseCase.d.ts +1 -3
  54. package/types/domain/usecases/HandleScheduledEventUseCase.d.ts.map +1 -1
  55. package/types/domain/usecases/IssueRejectionEvaluator.d.ts +0 -1
  56. package/types/domain/usecases/IssueRejectionEvaluator.d.ts.map +1 -1
  57. package/types/domain/usecases/NotifyFinishedIssuePreparationUseCase.d.ts +0 -1
  58. package/types/domain/usecases/NotifyFinishedIssuePreparationUseCase.d.ts.map +1 -1
  59. package/types/domain/usecases/RevertNotReadyReviewQueueIssueUseCase.d.ts.map +1 -1
  60. package/types/domain/usecases/RevertOrphanedPreparationUseCase.d.ts +0 -1
  61. package/types/domain/usecases/RevertOrphanedPreparationUseCase.d.ts.map +1 -1
  62. package/types/domain/usecases/autoStatusCheckComments.d.ts +0 -4
  63. package/types/domain/usecases/autoStatusCheckComments.d.ts.map +1 -1
  64. package/types/domain/usecases/extractNextStepAgentFromComments.d.ts +5 -0
  65. package/types/domain/usecases/extractNextStepAgentFromComments.d.ts.map +1 -0
  66. package/types/domain/usecases/triagerAgentName.d.ts +3 -0
  67. package/types/domain/usecases/triagerAgentName.d.ts.map +1 -0
  68. package/src/domain/usecases/TriagerApprovalDispatchUseCase.test.ts +0 -1051
  69. package/src/domain/usecases/TriagerApprovalDispatchUseCase.ts +0 -274
  70. package/src/domain/usecases/awaitingOwnerApprovalMessage.ts +0 -5
  71. package/src/domain/usecases/isPullRequestDeclaredUnnecessary.test.ts +0 -162
  72. package/src/domain/usecases/isPullRequestDeclaredUnnecessary.ts +0 -29
  73. package/src/domain/usecases/isWaitingForOwnerApproval.test.ts +0 -91
  74. package/src/domain/usecases/isWaitingForOwnerApproval.ts +0 -17
  75. package/src/domain/usecases/returnedToAwaitingWorkspaceMessage.ts +0 -5
@@ -122,7 +122,6 @@ type NotifyFinishedOptions = {
122
122
  missingAgentName?: string;
123
123
  sessionErrorLine?: string;
124
124
  deferPreparation?: boolean;
125
- ownerApprovalTimeoutCycles?: string;
126
125
  };
127
126
 
128
127
  type CheckIssueReviewReadinessOptions = {
@@ -484,7 +483,6 @@ program
484
483
  labelsAsLlmAgentName: config.labelsAsLlmAgentName ?? null,
485
484
  labelsNotRequiringPullRequest:
486
485
  config.labelsNotRequiringPullRequest ?? null,
487
- ownerApprovalTimeoutCycles: config.ownerApprovalTimeoutCycles ?? 12,
488
486
  });
489
487
  }
490
488
 
@@ -585,10 +583,6 @@ program
585
583
  '--deferPreparation',
586
584
  'Defer the item via the Reactivation Trigger fields (sets nextActionDate to tomorrow) without creating any issue; use for transient upstream failures',
587
585
  )
588
- .option(
589
- '--ownerApprovalTimeoutCycles <count>',
590
- 'Number of owner-approval wait cycles before escalating to Failed Preparation (default: 12)',
591
- )
592
586
  .action(async (options: NotifyFinishedOptions) => {
593
587
  const token = process.env.GH_TOKEN;
594
588
  if (!token) {
@@ -730,9 +724,6 @@ program
730
724
  manager: config.manager ?? null,
731
725
  developerAgentName: config.developerAgentName ?? null,
732
726
  deferPreparation: options.deferPreparation ?? null,
733
- ownerApprovalTimeoutCycles: options.ownerApprovalTimeoutCycles
734
- ? Number(options.ownerApprovalTimeoutCycles)
735
- : (config.ownerApprovalTimeoutCycles ?? 12),
736
727
  });
737
728
  });
738
729
 
@@ -32,7 +32,6 @@ export type ConfigFile = {
32
32
  consoleProjects?: Record<string, string>;
33
33
  consoleGithubTokenFileDir?: string;
34
34
  disks?: DiskConfig[];
35
- ownerApprovalTimeoutCycles?: number;
36
35
  errorReportingRepository?: string;
37
36
  };
38
37
 
@@ -209,10 +208,6 @@ export const loadConfigFile = (configFilePath: string): ConfigFile => {
209
208
  'consoleGithubTokenFileDir',
210
209
  ),
211
210
  disks: getDisksValue(parsed, 'disks'),
212
- ownerApprovalTimeoutCycles: getNumberValue(
213
- parsed,
214
- 'ownerApprovalTimeoutCycles',
215
- ),
216
211
  errorReportingRepository: getStringValue(
217
212
  parsed,
218
213
  'errorReportingRepository',
@@ -410,9 +405,6 @@ export const mergeConfigs = (
410
405
  cliOverrides.consoleGithubTokenFileDir ??
411
406
  configFile.consoleGithubTokenFileDir,
412
407
  disks: cliOverrides.disks ?? configFile.disks,
413
- ownerApprovalTimeoutCycles:
414
- cliOverrides.ownerApprovalTimeoutCycles ??
415
- configFile.ownerApprovalTimeoutCycles,
416
408
  errorReportingRepository:
417
409
  readmeOverrides.errorReportingRepository ??
418
410
  cliOverrides.errorReportingRepository ??
@@ -74,7 +74,6 @@ import { ProxyRateLimitCacheRepository } from '../../repositories/ProxyRateLimit
74
74
  import { UpdateRateLimitCacheUseCase } from '../../../domain/usecases/UpdateRateLimitCacheUseCase';
75
75
  import { RevertOrphanedPreparationUseCase } from '../../../domain/usecases/RevertOrphanedPreparationUseCase';
76
76
  import { RevertNotReadyReviewQueueIssueUseCase } from '../../../domain/usecases/RevertNotReadyReviewQueueIssueUseCase';
77
- import { TriagerApprovalDispatchUseCase } from '../../../domain/usecases/TriagerApprovalDispatchUseCase';
78
77
  import { AgentDesignationLabelAdoptUseCase } from '../../../domain/usecases/AgentDesignationLabelAdoptUseCase';
79
78
  import { GitHubIssueCommentRepository } from '../../repositories/GitHubIssueCommentRepository';
80
79
  import { ProjectRequiredFieldCreateUseCase } from '../../../domain/usecases/ProjectRequiredFieldCreateUseCase';
@@ -528,11 +527,6 @@ export class HandleScheduledEventUseCaseHandler {
528
527
  issueRepository,
529
528
  issueCommentRepository,
530
529
  );
531
- const triagerApprovalDispatchUseCase = new TriagerApprovalDispatchUseCase(
532
- projectRepository,
533
- issueRepository,
534
- issueCommentRepository,
535
- );
536
530
  const agentDesignationLabelAdoptUseCase =
537
531
  new AgentDesignationLabelAdoptUseCase(projectRepository, issueRepository);
538
532
 
@@ -575,7 +569,6 @@ export class HandleScheduledEventUseCaseHandler {
575
569
  revertOrphanedPreparationUseCase,
576
570
  conflictedIssueRevertUseCase,
577
571
  revertNotReadyReviewQueueIssueUseCase,
578
- triagerApprovalDispatchUseCase,
579
572
  agentDesignationLabelAdoptUseCase,
580
573
  updateRateLimitCacheUseCase,
581
574
  dailySecurityScanUseCase,
@@ -212,13 +212,13 @@ describe('CheckIssueReviewReadinessUseCase', () => {
212
212
  });
213
213
  });
214
214
 
215
- it('should return reviewReady=true when the last report declares pullRequestRequired as false and no related PR exists', async () => {
215
+ it('should return reviewReady=true when the last report designates the triager and no related PR exists', async () => {
216
216
  const issue = createMockIssue();
217
217
  mockIssueRepository.getIssueByUrl.mockResolvedValue(issue);
218
218
  mockIssueCommentRepository.getCommentsFromIssue.mockResolvedValue([
219
219
  createMockComment({
220
220
  content:
221
- 'From: :robot: Agent report\n```json\n{"pullRequestRequired": false}\n```',
221
+ 'From: :robot: Agent report\n```json\n{"nextStepAgent": "triager"}\n```',
222
222
  }),
223
223
  ]);
224
224
  mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([]);
@@ -232,13 +232,35 @@ describe('CheckIssueReviewReadinessUseCase', () => {
232
232
  expect(result.reviewReady).toBe(true);
233
233
  });
234
234
 
235
- it('should keep every rejection other than PULL_REQUEST_NOT_FOUND when the last report declares pullRequestRequired as false', async () => {
235
+ it('should keep rejecting a missing PR when the last report designates an agent other than the triager', async () => {
236
236
  const issue = createMockIssue();
237
237
  mockIssueRepository.getIssueByUrl.mockResolvedValue(issue);
238
238
  mockIssueCommentRepository.getCommentsFromIssue.mockResolvedValue([
239
239
  createMockComment({
240
240
  content:
241
- 'From: :robot: Agent report\n```json\n{"pullRequestRequired": false}\n```',
241
+ 'From: :robot: Agent report\n```json\n{"nextStepAgent": "developer"}\n```',
242
+ }),
243
+ ]);
244
+ mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([]);
245
+
246
+ const result = await useCase.run({
247
+ issueUrl: 'https://github.com/user/repo/issues/1',
248
+ });
249
+
250
+ expect(result.reviewReady).toBe(false);
251
+ expect(result.rejections).toContainEqual({
252
+ type: 'PULL_REQUEST_NOT_FOUND',
253
+ detail: 'PULL_REQUEST_NOT_FOUND',
254
+ });
255
+ });
256
+
257
+ it('should keep every rejection other than PULL_REQUEST_NOT_FOUND when the last report designates the triager', async () => {
258
+ const issue = createMockIssue();
259
+ mockIssueRepository.getIssueByUrl.mockResolvedValue(issue);
260
+ mockIssueCommentRepository.getCommentsFromIssue.mockResolvedValue([
261
+ createMockComment({
262
+ content:
263
+ 'From: :robot: Agent report\n```json\n{"nextStepAgent": "triager"}\n```',
242
264
  }),
243
265
  ]);
244
266
  mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([
@@ -5,10 +5,11 @@ import {
5
5
  PrRejectedReasonType,
6
6
  } from './IssueRejectionEvaluator';
7
7
  import { resolveLabelsNotRequiringPullRequest } from './resolveLabelsNotRequiringPullRequest';
8
- import { isPullRequestDeclaredUnnecessary } from './isPullRequestDeclaredUnnecessary';
9
8
  import { normalizeReportBody } from './normalizeReportBody';
10
9
  import { isAgentReportBody } from './isAgentReportBody';
11
10
  import { isAuthorAuthorizedForAutoStatusCheck } from './isAuthorAuthorizedForAutoStatusCheck';
11
+ import { extractNextStepAgentFromComments } from './extractNextStepAgentFromComments';
12
+ import { isTriagerAgentName } from './triagerAgentName';
12
13
 
13
14
  type RejectedReasonType =
14
15
  | 'ISSUE_NOT_FOUND'
@@ -94,10 +95,11 @@ export class CheckIssueReviewReadinessUseCase {
94
95
  { developerAgentName: params.developerAgentName },
95
96
  );
96
97
 
97
- const requiredPrRejections = isPullRequestDeclaredUnnecessary(
98
+ const nextStepAgent = extractNextStepAgentFromComments(
98
99
  comments,
99
100
  isTrustedAuthor,
100
- )
101
+ );
102
+ const requiredPrRejections = isTriagerAgentName(nextStepAgent)
101
103
  ? prRejections.filter(
102
104
  (rejection) => rejection.type !== 'PULL_REQUEST_NOT_FOUND',
103
105
  )
@@ -25,7 +25,6 @@ import { StartPreparationUseCase } from './StartPreparationUseCase';
25
25
  import { RevertOrphanedPreparationUseCase } from './RevertOrphanedPreparationUseCase';
26
26
  import { ConflictedIssueRevertUseCase } from './ConflictedIssueRevertUseCase';
27
27
  import { RevertNotReadyReviewQueueIssueUseCase } from './RevertNotReadyReviewQueueIssueUseCase';
28
- import { TriagerApprovalDispatchUseCase } from './TriagerApprovalDispatchUseCase';
29
28
  import { AgentDesignationLabelAdoptUseCase } from './AgentDesignationLabelAdoptUseCase';
30
29
  import { ProjectRequiredFieldCreateUseCase } from './ProjectRequiredFieldCreateUseCase';
31
30
  import { SetupTowerDefenceProjectUseCase } from './SetupTowerDefenceProjectUseCase';
@@ -129,8 +128,6 @@ describe('HandleScheduledEventUseCase', () => {
129
128
  mock<ConflictedIssueRevertUseCase>();
130
129
  const mockRevertNotReadyReviewQueueIssueUseCase =
131
130
  mock<RevertNotReadyReviewQueueIssueUseCase>();
132
- const mockTriagerApprovalDispatchUseCase =
133
- mock<TriagerApprovalDispatchUseCase>();
134
131
  const mockAgentDesignationLabelAdoptUseCase =
135
132
  mock<AgentDesignationLabelAdoptUseCase>();
136
133
  const mockUpdateRateLimitCacheUseCase = mock<UpdateRateLimitCacheUseCase>();
@@ -165,7 +162,6 @@ describe('HandleScheduledEventUseCase', () => {
165
162
  mockRevertOrphanedPreparationUseCase,
166
163
  mockConflictedIssueRevertUseCase,
167
164
  mockRevertNotReadyReviewQueueIssueUseCase,
168
- mockTriagerApprovalDispatchUseCase,
169
165
  mockAgentDesignationLabelAdoptUseCase,
170
166
  mockUpdateRateLimitCacheUseCase,
171
167
  mockDailySecurityScanUseCase,
@@ -282,29 +278,6 @@ describe('HandleScheduledEventUseCase', () => {
282
278
  });
283
279
  });
284
280
 
285
- it('should call TriagerApprovalDispatchUseCase with project URL', async () => {
286
- const input = {
287
- projectName: 'test-project',
288
- org: 'test-org',
289
- projectUrl: 'https://github.com/test-org/test-project',
290
- manager: 'test-manager',
291
- workingReport: {
292
- repo: 'test-repo',
293
- members: ['member1'],
294
- spreadsheetUrl: 'https://docs.google.com/spreadsheets/test',
295
- },
296
- urlOfStoryView: 'https://github.com/test-org/test-project/issues',
297
- disabled: false,
298
- };
299
-
300
- await useCase.run(input);
301
- expect(mockTriagerApprovalDispatchUseCase.run).toHaveBeenCalledWith(
302
- expect.objectContaining({
303
- projectUrl: 'https://github.com/test-org/test-project',
304
- }),
305
- );
306
- });
307
-
308
281
  it('should return null and skip all processing when disabled is true', async () => {
309
282
  const input = {
310
283
  projectName: 'test-project',
@@ -1827,7 +1800,6 @@ describe('HandleScheduledEventUseCase', () => {
1827
1800
  mock<RevertOrphanedPreparationUseCase>(),
1828
1801
  mock<ConflictedIssueRevertUseCase>(),
1829
1802
  mock<RevertNotReadyReviewQueueIssueUseCase>(),
1830
- mock<TriagerApprovalDispatchUseCase>(),
1831
1803
  mock<AgentDesignationLabelAdoptUseCase>(),
1832
1804
  null,
1833
1805
  null,
@@ -28,7 +28,6 @@ import {
28
28
  import { AgentDesignationLabelAdoptUseCase } from './AgentDesignationLabelAdoptUseCase';
29
29
  import { RevertOrphanedPreparationUseCase } from './RevertOrphanedPreparationUseCase';
30
30
  import { RevertNotReadyReviewQueueIssueUseCase } from './RevertNotReadyReviewQueueIssueUseCase';
31
- import { TriagerApprovalDispatchUseCase } from './TriagerApprovalDispatchUseCase';
32
31
  import { isRecord } from './isRecord';
33
32
  import { resolveLabelsAsLlmAgentName } from './resolveLabelsAsLlmAgentName';
34
33
  import { resolveAllowedIssueAuthors } from './resolveAllowedIssueAuthors';
@@ -139,7 +138,6 @@ export class HandleScheduledEventUseCase {
139
138
  readonly revertOrphanedPreparationUseCase: RevertOrphanedPreparationUseCase,
140
139
  readonly conflictedIssueRevertUseCase: ConflictedIssueRevertUseCase,
141
140
  readonly revertNotReadyReviewQueueIssueUseCase: RevertNotReadyReviewQueueIssueUseCase,
142
- readonly triagerApprovalDispatchUseCase: TriagerApprovalDispatchUseCase,
143
141
  readonly agentDesignationLabelAdoptUseCase: AgentDesignationLabelAdoptUseCase,
144
142
  readonly updateRateLimitCacheUseCase: UpdateRateLimitCacheUseCase | null,
145
143
  readonly dailySecurityScanUseCase: DailySecurityScanUseCase | null,
@@ -452,10 +450,6 @@ ${JSON.stringify(e)}
452
450
  allowedIssueAuthors,
453
451
  developerAgentName: input.developerAgentName,
454
452
  });
455
- await this.triagerApprovalDispatchUseCase.run({
456
- projectUrl: input.projectUrl,
457
- allowedIssueAuthors,
458
- });
459
453
  if (this.dailySecurityScanUseCase !== null && input.dailySecurityScan) {
460
454
  await this.dailySecurityScanUseCase.run({
461
455
  targetDates: targetDateTimes,
@@ -739,125 +739,6 @@ describe('IssueRejectionEvaluator', () => {
739
739
  });
740
740
  });
741
741
 
742
- describe('pullRequestRequired: false in issue body', () => {
743
- it('should not reject and not call findRelatedOpenPRs when body ends with JSON block containing pullRequestRequired: false', async () => {
744
- const body = '```json\n{"pullRequestRequired": false}\n```';
745
-
746
- const result = await evaluator.evaluate({
747
- url: 'https://github.com/user/repo/issues/1',
748
- labels: [],
749
- isPr: false,
750
- body,
751
- });
752
-
753
- expect(mockIssueRepository.findRelatedOpenPRs).not.toHaveBeenCalled();
754
- expect(result.rejections).toHaveLength(0);
755
- expect(result.approvedPrUrl).toBeNull();
756
- });
757
-
758
- it('should not reject even with trailing whitespace after the closing JSON block', async () => {
759
- const body = '```json\n{"pullRequestRequired": false}\n```\n ';
760
-
761
- const result = await evaluator.evaluate({
762
- url: 'https://github.com/user/repo/issues/1',
763
- labels: [],
764
- isPr: false,
765
- body,
766
- });
767
-
768
- expect(mockIssueRepository.findRelatedOpenPRs).not.toHaveBeenCalled();
769
- expect(result.rejections).toHaveLength(0);
770
- });
771
-
772
- it('should still require PR when body has pullRequestRequired: true', async () => {
773
- mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([]);
774
- const body = '```json\n{"pullRequestRequired": true}\n```';
775
-
776
- const result = await evaluator.evaluate({
777
- url: 'https://github.com/user/repo/issues/1',
778
- labels: [],
779
- isPr: false,
780
- body,
781
- });
782
-
783
- expect(result.rejections).toHaveLength(1);
784
- expect(result.rejections[0].type).toBe('PULL_REQUEST_NOT_FOUND');
785
- });
786
-
787
- it('should still require PR when body is null', async () => {
788
- mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([]);
789
-
790
- const result = await evaluator.evaluate({
791
- url: 'https://github.com/user/repo/issues/1',
792
- labels: [],
793
- isPr: false,
794
- body: null,
795
- });
796
-
797
- expect(result.rejections).toHaveLength(1);
798
- expect(result.rejections[0].type).toBe('PULL_REQUEST_NOT_FOUND');
799
- });
800
-
801
- it('should still require PR when body is undefined', async () => {
802
- mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([]);
803
-
804
- const result = await evaluator.evaluate({
805
- url: 'https://github.com/user/repo/issues/1',
806
- labels: [],
807
- isPr: false,
808
- });
809
-
810
- expect(result.rejections).toHaveLength(1);
811
- expect(result.rejections[0].type).toBe('PULL_REQUEST_NOT_FOUND');
812
- });
813
-
814
- it('should still require PR when JSON block with pullRequestRequired: false is not at end of body', async () => {
815
- mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([]);
816
- const body =
817
- '```json\n{"pullRequestRequired": false}\n```\n\nsome text after';
818
-
819
- const result = await evaluator.evaluate({
820
- url: 'https://github.com/user/repo/issues/1',
821
- labels: [],
822
- isPr: false,
823
- body,
824
- });
825
-
826
- expect(result.rejections).toHaveLength(1);
827
- expect(result.rejections[0].type).toBe('PULL_REQUEST_NOT_FOUND');
828
- });
829
-
830
- it('should still require PR when body has invalid JSON in code block', async () => {
831
- mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([]);
832
- const body = '```json\n{invalid json}\n```';
833
-
834
- const result = await evaluator.evaluate({
835
- url: 'https://github.com/user/repo/issues/1',
836
- labels: [],
837
- isPr: false,
838
- body,
839
- });
840
-
841
- expect(result.rejections).toHaveLength(1);
842
- expect(result.rejections[0].type).toBe('PULL_REQUEST_NOT_FOUND');
843
- });
844
-
845
- it('should still require PR when body has pullRequestRequired not set to false', async () => {
846
- mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([]);
847
- const body = '```json\n{"someOtherField": true}\n```';
848
-
849
- const result = await evaluator.evaluate({
850
- url: 'https://github.com/user/repo/issues/1',
851
- labels: [],
852
- isPr: false,
853
- body,
854
- });
855
-
856
- expect(result.rejections).toHaveLength(1);
857
- expect(result.rejections[0].type).toBe('PULL_REQUEST_NOT_FOUND');
858
- });
859
- });
860
-
861
742
  describe('detectConflictEvenIfEvaluationSkipped option', () => {
862
743
  it('should call findRelatedOpenPRs and return PULL_REQUEST_CONFLICTED when relatedOpenPrUrls is null and PR is conflicted', async () => {
863
744
  mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([
@@ -2,7 +2,6 @@ import {
2
2
  IssueRepository,
3
3
  RelatedPullRequest,
4
4
  } from './adapter-interfaces/IssueRepository';
5
- import { normalizeReportBody } from './normalizeReportBody';
6
5
 
7
6
  export type PrRejectedReasonType =
8
7
  | 'PULL_REQUEST_NOT_FOUND'
@@ -243,7 +242,6 @@ export class IssueRejectionEvaluator {
243
242
  return (
244
243
  !isNonDeveloperAgent &&
245
244
  !hasLabelNotRequiringPullRequest &&
246
- this.isPullRequestRequiredByBody(issue.body) &&
247
245
  (categoryLabels.length <= 0 || categoryLabels.includes('category:e2e'))
248
246
  );
249
247
  };
@@ -337,23 +335,4 @@ export class IssueRejectionEvaluator {
337
335
  targetPath: string,
338
336
  ): boolean =>
339
337
  filePath === targetPath || filePath.startsWith(`${targetPath}/`);
340
-
341
- private isPullRequestRequiredByBody = (
342
- body: string | null | undefined,
343
- ): boolean => {
344
- if (!body) return true;
345
- const match = normalizeReportBody(body)
346
- .trimEnd()
347
- .match(/```json\n([\s\S]*?)\n```\s*$/);
348
- if (!match || !match[1]) return true;
349
- let config: unknown;
350
- try {
351
- config = JSON.parse(match[1]);
352
- } catch {
353
- return true;
354
- }
355
- if (typeof config !== 'object' || config === null) return true;
356
- if (!('pullRequestRequired' in config)) return true;
357
- return Reflect.get(config, 'pullRequestRequired') !== false;
358
- };
359
338
  }