github-issue-tower-defence-management 2.6.0 → 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 (96) hide show
  1. package/CHANGELOG.md +14 -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/adapter/entry-points/handlers/tokenExhaustionHandover.js +2 -1
  10. package/bin/adapter/entry-points/handlers/tokenExhaustionHandover.js.map +1 -1
  11. package/bin/adapter/repositories/GitHubIssueCheckpointRepository.js +37 -0
  12. package/bin/adapter/repositories/GitHubIssueCheckpointRepository.js.map +1 -0
  13. package/bin/domain/usecases/CheckIssueReviewReadinessUseCase.js +4 -2
  14. package/bin/domain/usecases/CheckIssueReviewReadinessUseCase.js.map +1 -1
  15. package/bin/domain/usecases/HandleScheduledEventUseCase.js +1 -6
  16. package/bin/domain/usecases/HandleScheduledEventUseCase.js.map +1 -1
  17. package/bin/domain/usecases/IssueRejectionEvaluator.js +0 -23
  18. package/bin/domain/usecases/IssueRejectionEvaluator.js.map +1 -1
  19. package/bin/domain/usecases/NotifyFinishedIssuePreparationUseCase.js +39 -69
  20. package/bin/domain/usecases/NotifyFinishedIssuePreparationUseCase.js.map +1 -1
  21. package/bin/domain/usecases/RevertNotReadyReviewQueueIssueUseCase.js +3 -2
  22. package/bin/domain/usecases/RevertNotReadyReviewQueueIssueUseCase.js.map +1 -1
  23. package/bin/domain/usecases/RevertOrphanedPreparationUseCase.js +0 -43
  24. package/bin/domain/usecases/RevertOrphanedPreparationUseCase.js.map +1 -1
  25. package/bin/domain/usecases/TokenExhaustionHandoverUseCase.js +10 -2
  26. package/bin/domain/usecases/TokenExhaustionHandoverUseCase.js.map +1 -1
  27. package/bin/domain/usecases/adapter-interfaces/IssueCheckpointRepository.js +3 -0
  28. package/bin/domain/usecases/adapter-interfaces/IssueCheckpointRepository.js.map +1 -0
  29. package/bin/domain/usecases/autoStatusCheckComments.js +1 -11
  30. package/bin/domain/usecases/autoStatusCheckComments.js.map +1 -1
  31. package/bin/domain/usecases/extractNextStepAgentFromComments.js +14 -0
  32. package/bin/domain/usecases/extractNextStepAgentFromComments.js.map +1 -0
  33. package/bin/domain/usecases/triagerAgentName.js +7 -0
  34. package/bin/domain/usecases/triagerAgentName.js.map +1 -0
  35. package/package.json +1 -1
  36. package/src/adapter/entry-points/cli/index.test.ts +0 -2
  37. package/src/adapter/entry-points/cli/index.ts +0 -9
  38. package/src/adapter/entry-points/cli/projectConfig.ts +0 -8
  39. package/src/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.ts +0 -7
  40. package/src/adapter/entry-points/handlers/tokenExhaustionHandover.ts +2 -0
  41. package/src/adapter/repositories/GitHubIssueCheckpointRepository.test.ts +51 -0
  42. package/src/adapter/repositories/GitHubIssueCheckpointRepository.ts +47 -0
  43. package/src/domain/usecases/CheckIssueReviewReadinessUseCase.test.ts +26 -4
  44. package/src/domain/usecases/CheckIssueReviewReadinessUseCase.ts +5 -3
  45. package/src/domain/usecases/HandleScheduledEventUseCase.test.ts +0 -28
  46. package/src/domain/usecases/HandleScheduledEventUseCase.ts +0 -6
  47. package/src/domain/usecases/IssueRejectionEvaluator.test.ts +0 -119
  48. package/src/domain/usecases/IssueRejectionEvaluator.ts +0 -21
  49. package/src/domain/usecases/NotifyFinishedIssuePreparationUseCase.test.ts +185 -327
  50. package/src/domain/usecases/NotifyFinishedIssuePreparationUseCase.ts +68 -138
  51. package/src/domain/usecases/RevertNotReadyReviewQueueIssueUseCase.test.ts +4 -4
  52. package/src/domain/usecases/RevertNotReadyReviewQueueIssueUseCase.ts +11 -5
  53. package/src/domain/usecases/RevertOrphanedPreparationUseCase.test.ts +0 -313
  54. package/src/domain/usecases/RevertOrphanedPreparationUseCase.ts +1 -102
  55. package/src/domain/usecases/TokenExhaustionHandoverUseCase.test.ts +87 -3
  56. package/src/domain/usecases/TokenExhaustionHandoverUseCase.ts +14 -1
  57. package/src/domain/usecases/adapter-interfaces/IssueCheckpointRepository.ts +3 -0
  58. package/src/domain/usecases/autoStatusCheckComments.ts +0 -17
  59. package/src/domain/usecases/extractNextStepAgentFromComments.ts +13 -0
  60. package/src/domain/usecases/isHumanComment.test.ts +0 -13
  61. package/src/domain/usecases/triagerAgentName.ts +4 -0
  62. package/types/adapter/entry-points/cli/index.d.ts.map +1 -1
  63. package/types/adapter/entry-points/cli/projectConfig.d.ts +0 -1
  64. package/types/adapter/entry-points/cli/projectConfig.d.ts.map +1 -1
  65. package/types/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.d.ts.map +1 -1
  66. package/types/adapter/entry-points/handlers/tokenExhaustionHandover.d.ts.map +1 -1
  67. package/types/adapter/repositories/GitHubIssueCheckpointRepository.d.ts +8 -0
  68. package/types/adapter/repositories/GitHubIssueCheckpointRepository.d.ts.map +1 -0
  69. package/types/domain/usecases/CheckIssueReviewReadinessUseCase.d.ts.map +1 -1
  70. package/types/domain/usecases/HandleScheduledEventUseCase.d.ts +1 -3
  71. package/types/domain/usecases/HandleScheduledEventUseCase.d.ts.map +1 -1
  72. package/types/domain/usecases/IssueRejectionEvaluator.d.ts +0 -1
  73. package/types/domain/usecases/IssueRejectionEvaluator.d.ts.map +1 -1
  74. package/types/domain/usecases/NotifyFinishedIssuePreparationUseCase.d.ts +0 -1
  75. package/types/domain/usecases/NotifyFinishedIssuePreparationUseCase.d.ts.map +1 -1
  76. package/types/domain/usecases/RevertNotReadyReviewQueueIssueUseCase.d.ts.map +1 -1
  77. package/types/domain/usecases/RevertOrphanedPreparationUseCase.d.ts +0 -1
  78. package/types/domain/usecases/RevertOrphanedPreparationUseCase.d.ts.map +1 -1
  79. package/types/domain/usecases/TokenExhaustionHandoverUseCase.d.ts +3 -1
  80. package/types/domain/usecases/TokenExhaustionHandoverUseCase.d.ts.map +1 -1
  81. package/types/domain/usecases/adapter-interfaces/IssueCheckpointRepository.d.ts +4 -0
  82. package/types/domain/usecases/adapter-interfaces/IssueCheckpointRepository.d.ts.map +1 -0
  83. package/types/domain/usecases/autoStatusCheckComments.d.ts +0 -4
  84. package/types/domain/usecases/autoStatusCheckComments.d.ts.map +1 -1
  85. package/types/domain/usecases/extractNextStepAgentFromComments.d.ts +5 -0
  86. package/types/domain/usecases/extractNextStepAgentFromComments.d.ts.map +1 -0
  87. package/types/domain/usecases/triagerAgentName.d.ts +3 -0
  88. package/types/domain/usecases/triagerAgentName.d.ts.map +1 -0
  89. package/src/domain/usecases/TriagerApprovalDispatchUseCase.test.ts +0 -1051
  90. package/src/domain/usecases/TriagerApprovalDispatchUseCase.ts +0 -274
  91. package/src/domain/usecases/awaitingOwnerApprovalMessage.ts +0 -5
  92. package/src/domain/usecases/isPullRequestDeclaredUnnecessary.test.ts +0 -162
  93. package/src/domain/usecases/isPullRequestDeclaredUnnecessary.ts +0 -29
  94. package/src/domain/usecases/isWaitingForOwnerApproval.test.ts +0 -91
  95. package/src/domain/usecases/isWaitingForOwnerApproval.ts +0 -17
  96. package/src/domain/usecases/returnedToAwaitingWorkspaceMessage.ts +0 -5
@@ -154,7 +154,17 @@ describe('NotifyFinishedIssuePreparationUseCase', () => {
154
154
  updateStatus: jest.fn(),
155
155
  updateLabels: jest.fn().mockResolvedValue(undefined),
156
156
  getOrCreateLabel: jest.fn().mockResolvedValue(undefined),
157
- findRelatedOpenPRs: jest.fn(),
157
+ findRelatedOpenPRs: jest.fn().mockResolvedValue([
158
+ {
159
+ url: 'https://github.com/user/repo/pull/1',
160
+ isConflicted: false,
161
+ isPassedAllCiJob: true,
162
+ isCiStateSuccess: true,
163
+ isResolvedAllReviewComments: true,
164
+ isBranchOutOfDate: false,
165
+ missingRequiredCheckNames: [],
166
+ },
167
+ ]),
158
168
  getOpenPullRequest: jest.fn(),
159
169
  getPullRequestChangedFilePaths: jest.fn().mockResolvedValue([]),
160
170
  approvePullRequest: jest.fn().mockResolvedValue(undefined),
@@ -248,7 +258,7 @@ describe('NotifyFinishedIssuePreparationUseCase', () => {
248
258
  mockProjectRepository.getByUrl.mockResolvedValue(mockProject);
249
259
  mockIssueRepository.get.mockResolvedValue(issue);
250
260
  mockIssueCommentRepository.getCommentsFromIssue.mockResolvedValue([
251
- createMockComment({ content: 'From: :robot: Agent report' }),
261
+ createMockComment({ content: 'From: :robot: agent (model)' }),
252
262
  ]);
253
263
  mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([
254
264
  {
@@ -350,7 +360,7 @@ describe('NotifyFinishedIssuePreparationUseCase', () => {
350
360
  mockProjectRepository.getByUrl.mockResolvedValue(mockProject);
351
361
  mockIssueRepository.get.mockResolvedValue(prIssue);
352
362
  mockIssueCommentRepository.getCommentsFromIssue.mockResolvedValue([
353
- createMockComment({ content: 'From: :robot: Agent report' }),
363
+ createMockComment({ content: 'From: :robot: agent (model)' }),
354
364
  ]);
355
365
  mockIssueRepository.getOpenPullRequest.mockResolvedValue({
356
366
  url: 'https://github.com/user/repo/pull/77',
@@ -849,7 +859,7 @@ describe('NotifyFinishedIssuePreparationUseCase', () => {
849
859
  mockIssueCommentRepository.getCommentsFromIssue.mockResolvedValue([
850
860
  createMockComment({
851
861
  content:
852
- 'From: :robot: Agent report\n```json\n{"nextStep": "Fix the tests"}\n```',
862
+ 'From: :robot: agent (model)\n```json\n{"nextStep": "Fix the tests"}\n```',
853
863
  }),
854
864
  ]);
855
865
  mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([
@@ -892,7 +902,8 @@ describe('NotifyFinishedIssuePreparationUseCase', () => {
892
902
  mockIssueRepository.get.mockResolvedValue(issue);
893
903
  mockIssueCommentRepository.getCommentsFromIssue.mockResolvedValue([
894
904
  createMockComment({
895
- content: 'From: :robot: Agent report\n```json\n{"nextStep": null}\n```',
905
+ content:
906
+ 'From: :robot: agent (model)\n```json\n{"nextStep": null}\n```',
896
907
  }),
897
908
  ]);
898
909
  mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([
@@ -935,7 +946,7 @@ describe('NotifyFinishedIssuePreparationUseCase', () => {
935
946
  mockIssueCommentRepository.getCommentsFromIssue.mockResolvedValue([
936
947
  createMockComment({
937
948
  content:
938
- 'From: :robot: Agent report\n```json\n{"nextStepAgent": "llm-agent:chore", "nextStep": null}\n```',
949
+ 'From: :robot: agent (model)\n```json\n{"nextStepAgent": "llm-agent:chore", "nextStep": null}\n```',
939
950
  }),
940
951
  ]);
941
952
 
@@ -1197,7 +1208,7 @@ describe('NotifyFinishedIssuePreparationUseCase', () => {
1197
1208
  mockIssueCommentRepository.getCommentsFromIssue.mockResolvedValue([
1198
1209
  createMockComment({
1199
1210
  content:
1200
- 'From: :robot: Agent report\n```json\n{"nextStepAgent": "llm-agent:impl", "nextStep": null}\n```',
1211
+ 'From: :robot: agent (model)\n```json\n{"nextStepAgent": "llm-agent:impl", "nextStep": null}\n```',
1201
1212
  }),
1202
1213
  ]);
1203
1214
 
@@ -1224,7 +1235,7 @@ describe('NotifyFinishedIssuePreparationUseCase', () => {
1224
1235
  mockIssueCommentRepository.getCommentsFromIssue.mockResolvedValue([
1225
1236
  createMockComment({
1226
1237
  content:
1227
- 'From: :robot: Agent report\n```json\n{"nextStepAgent": null, "nextStep": null}\n```',
1238
+ 'From: :robot: agent (model)\n```json\n{"nextStepAgent": null, "nextStep": null}\n```',
1228
1239
  }),
1229
1240
  ]);
1230
1241
  mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([
@@ -1269,7 +1280,7 @@ describe('NotifyFinishedIssuePreparationUseCase', () => {
1269
1280
  mockIssueCommentRepository.getCommentsFromIssue.mockResolvedValue([
1270
1281
  createMockComment({
1271
1282
  content:
1272
- 'From: :robot: Agent report\n```json\n{"nextStepAgent": "llm-agent:new-agent", "nextStep": null}\n```',
1283
+ 'From: :robot: agent (model)\n```json\n{"nextStepAgent": "llm-agent:new-agent", "nextStep": null}\n```',
1273
1284
  }),
1274
1285
  ]);
1275
1286
 
@@ -1298,7 +1309,7 @@ describe('NotifyFinishedIssuePreparationUseCase', () => {
1298
1309
  mockIssueCommentRepository.getCommentsFromIssue.mockResolvedValue([
1299
1310
  createMockComment({
1300
1311
  content:
1301
- 'From: :robot: Agent report\n```json\n{"nextStepAgent": "llm-agent:impl", "nextStep": null}\n```',
1312
+ 'From: :robot: agent (model)\n```json\n{"nextStepAgent": "llm-agent:impl", "nextStep": null}\n```',
1302
1313
  }),
1303
1314
  ]);
1304
1315
 
@@ -1326,7 +1337,7 @@ describe('NotifyFinishedIssuePreparationUseCase', () => {
1326
1337
  mockIssueCommentRepository.getCommentsFromIssue.mockResolvedValue([
1327
1338
  createMockComment({
1328
1339
  content:
1329
- 'From: :robot: Agent report\n```json\n{"nextStepAgent": "", "nextStep": null}\n```',
1340
+ 'From: :robot: agent (model)\n```json\n{"nextStepAgent": "", "nextStep": null}\n```',
1330
1341
  }),
1331
1342
  ]);
1332
1343
  mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([
@@ -1673,200 +1684,7 @@ describe('NotifyFinishedIssuePreparationUseCase', () => {
1673
1684
  );
1674
1685
  });
1675
1686
 
1676
- it('should not reject a missing PR when the last report declares pullRequestRequired as false', async () => {
1677
- const issue = createMockIssue({
1678
- url: 'https://github.com/user/repo/issues/1',
1679
- status: 'Preparation',
1680
- });
1681
-
1682
- mockProjectRepository.getByUrl.mockResolvedValue(mockProject);
1683
- mockIssueRepository.get.mockResolvedValue(issue);
1684
- mockIssueCommentRepository.getCommentsFromIssue.mockResolvedValue([
1685
- createMockComment({
1686
- content:
1687
- 'From: :robot: Test report\n```json\n{"pullRequestRequired": false}\n```',
1688
- }),
1689
- ]);
1690
- mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([]);
1691
-
1692
- await useCase.run({
1693
- projectUrl: 'https://github.com/users/user/projects/1',
1694
- issueUrl: 'https://github.com/user/repo/issues/1',
1695
- thresholdForAutoReject: 3,
1696
- workflowBlockerResolvedWebhookUrl: null,
1697
- allowedIssueAuthors: ['test-user'],
1698
- });
1699
-
1700
- expect(mockIssueCommentRepository.createComment).not.toHaveBeenCalledWith(
1701
- expect.anything(),
1702
- expect.stringContaining('PULL_REQUEST_NOT_FOUND'),
1703
- );
1704
- expect(mockIssueRepository.update).toHaveBeenCalledWith(
1705
- expect.objectContaining({
1706
- status: 'Awaiting Workspace',
1707
- }),
1708
- mockProject,
1709
- );
1710
- });
1711
-
1712
- it('should return the issue to Awaiting Workspace and say why when the last report declares pullRequestRequired as false', async () => {
1713
- const issue = createMockIssue({
1714
- url: 'https://github.com/user/repo/issues/1',
1715
- status: 'Preparation',
1716
- });
1717
-
1718
- mockProjectRepository.getByUrl.mockResolvedValue(mockProject);
1719
- mockIssueRepository.get.mockResolvedValue(issue);
1720
- mockIssueCommentRepository.getCommentsFromIssue.mockResolvedValue([
1721
- createMockComment({
1722
- content:
1723
- 'From: :robot: Test report\n```json\n{"pullRequestRequired": false, "nextStep": null}\n```',
1724
- }),
1725
- ]);
1726
- mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([]);
1727
-
1728
- await useCase.run({
1729
- projectUrl: 'https://github.com/users/user/projects/1',
1730
- issueUrl: 'https://github.com/user/repo/issues/1',
1731
- thresholdForAutoReject: 3,
1732
- workflowBlockerResolvedWebhookUrl: null,
1733
- allowedIssueAuthors: ['test-user'],
1734
- });
1735
-
1736
- expect(mockIssueRepository.updateStatus).toHaveBeenCalledWith(
1737
- mockProject,
1738
- expect.objectContaining({ url: 'https://github.com/user/repo/issues/1' }),
1739
- 'awaiting-workspace-id',
1740
- );
1741
- expect(mockIssueCommentRepository.createComment).toHaveBeenCalledWith(
1742
- expect.objectContaining({ url: 'https://github.com/user/repo/issues/1' }),
1743
- expect.stringContaining(
1744
- 'Auto Status Check: RETURNED_TO_AWAITING_WORKSPACE',
1745
- ),
1746
- );
1747
- });
1748
-
1749
- it('should take the Awaiting Quality Check path when the declaration arrives again after an earlier return to Awaiting Workspace', async () => {
1750
- const issue = createMockIssue({
1751
- url: 'https://github.com/user/repo/issues/1',
1752
- status: 'Preparation',
1753
- });
1754
-
1755
- mockProjectRepository.getByUrl.mockResolvedValue(mockProject);
1756
- mockIssueRepository.get.mockResolvedValue(issue);
1757
- mockIssueCommentRepository.getCommentsFromIssue.mockResolvedValue([
1758
- createMockComment({
1759
- content:
1760
- 'Auto Status Check: RETURNED_TO_AWAITING_WORKSPACE\nThe report declared that this task needs no pull request.',
1761
- }),
1762
- createMockComment({
1763
- content:
1764
- 'From: :robot: Test report\n```json\n{"pullRequestRequired": false, "nextStep": null}\n```',
1765
- }),
1766
- ]);
1767
- mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([]);
1768
-
1769
- await useCase.run({
1770
- projectUrl: 'https://github.com/users/user/projects/1',
1771
- issueUrl: 'https://github.com/user/repo/issues/1',
1772
- thresholdForAutoReject: 3,
1773
- workflowBlockerResolvedWebhookUrl: null,
1774
- allowedIssueAuthors: ['test-user'],
1775
- });
1776
-
1777
- expect(mockIssueRepository.update).toHaveBeenCalledWith(
1778
- expect.objectContaining({
1779
- status: 'Awaiting Quality Check',
1780
- }),
1781
- mockProject,
1782
- );
1783
- expect(mockIssueCommentRepository.createComment).not.toHaveBeenCalledWith(
1784
- expect.anything(),
1785
- expect.stringContaining(
1786
- 'Auto Status Check: RETURNED_TO_AWAITING_WORKSPACE',
1787
- ),
1788
- );
1789
- });
1790
-
1791
- it('should take the Awaiting Quality Check path when a later report omits the next step agent an earlier report declared', async () => {
1792
- const issue = createMockIssue({
1793
- url: 'https://github.com/user/repo/issues/1',
1794
- status: 'Preparation',
1795
- });
1796
-
1797
- mockProjectRepository.getByUrl.mockResolvedValue(mockProject);
1798
- mockIssueRepository.get.mockResolvedValue(issue);
1799
- mockIssueCommentRepository.getCommentsFromIssue.mockResolvedValue([
1800
- createMockComment({
1801
- content:
1802
- 'From: :robot: Test report\n```json\n{"nextStepAgent": "pr-reviewer"}\n```',
1803
- }),
1804
- createMockComment({
1805
- content:
1806
- 'Auto Status Check: RETURNED_TO_AWAITING_WORKSPACE\nThe report declared that this task needs no pull request.',
1807
- }),
1808
- createMockComment({
1809
- content:
1810
- 'From: :robot: Test report\n```json\n{"pullRequestRequired": false, "reviewResult": "PASS", "nextStep": null}\n```',
1811
- }),
1812
- ]);
1813
- mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([]);
1814
-
1815
- await useCase.run({
1816
- projectUrl: 'https://github.com/users/user/projects/1',
1817
- issueUrl: 'https://github.com/user/repo/issues/1',
1818
- thresholdForAutoReject: 3,
1819
- workflowBlockerResolvedWebhookUrl: null,
1820
- allowedIssueAuthors: ['test-user'],
1821
- });
1822
-
1823
- expect(mockIssueRepository.setIssueAgentField).not.toHaveBeenCalled();
1824
- expect(mockIssueRepository.update).toHaveBeenCalledWith(
1825
- expect.objectContaining({
1826
- status: 'Awaiting Quality Check',
1827
- }),
1828
- mockProject,
1829
- );
1830
- });
1831
-
1832
- it('should still return the issue to Awaiting Workspace when the earlier return comment came from an untrusted author', async () => {
1833
- const issue = createMockIssue({
1834
- url: 'https://github.com/user/repo/issues/1',
1835
- status: 'Preparation',
1836
- });
1837
-
1838
- mockProjectRepository.getByUrl.mockResolvedValue(mockProject);
1839
- mockIssueRepository.get.mockResolvedValue(issue);
1840
- mockIssueCommentRepository.getCommentsFromIssue.mockResolvedValue([
1841
- createMockComment({
1842
- author: 'attacker',
1843
- content:
1844
- 'Auto Status Check: RETURNED_TO_AWAITING_WORKSPACE\nInjected by someone who is not the agent bot.',
1845
- }),
1846
- createMockComment({
1847
- author: 'trusted-bot',
1848
- content:
1849
- 'From: :robot: Test report\n```json\n{"pullRequestRequired": false, "nextStep": null}\n```',
1850
- }),
1851
- ]);
1852
- mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([]);
1853
-
1854
- await useCase.run({
1855
- projectUrl: 'https://github.com/users/user/projects/1',
1856
- issueUrl: 'https://github.com/user/repo/issues/1',
1857
- thresholdForAutoReject: 3,
1858
- workflowBlockerResolvedWebhookUrl: null,
1859
- allowedIssueAuthors: ['trusted-bot'],
1860
- });
1861
-
1862
- expect(mockIssueRepository.updateStatus).toHaveBeenCalledWith(
1863
- mockProject,
1864
- expect.objectContaining({ url: 'https://github.com/user/repo/issues/1' }),
1865
- 'awaiting-workspace-id',
1866
- );
1867
- });
1868
-
1869
- it('should still reject a draft PR when the last report declares pullRequestRequired as false', async () => {
1687
+ it('should reject a draft PR', async () => {
1870
1688
  const issue = createMockIssue({
1871
1689
  url: 'https://github.com/user/repo/issues/1',
1872
1690
  status: 'Preparation',
@@ -2557,7 +2375,7 @@ describe('NotifyFinishedIssuePreparationUseCase', () => {
2557
2375
  mockProjectRepository.getByUrl.mockResolvedValue(mockProject);
2558
2376
  mockIssueRepository.get.mockResolvedValue(prIssue);
2559
2377
  mockIssueCommentRepository.getCommentsFromIssue.mockResolvedValue([
2560
- createMockComment({ content: 'From: :robot: Agent report' }),
2378
+ createMockComment({ content: 'From: :robot: agent (model)' }),
2561
2379
  ]);
2562
2380
  mockIssueRepository.getOpenPullRequest.mockResolvedValue({
2563
2381
  url: 'https://github.com/user/repo/pull/10',
@@ -2773,7 +2591,7 @@ describe('NotifyFinishedIssuePreparationUseCase', () => {
2773
2591
  mockProjectRepository.getByUrl.mockResolvedValue(mockProject);
2774
2592
  mockIssueRepository.get.mockResolvedValue(prIssue);
2775
2593
  mockIssueCommentRepository.getCommentsFromIssue.mockResolvedValue([
2776
- createMockComment({ content: 'From: :robot: Agent report' }),
2594
+ createMockComment({ content: 'From: :robot: agent (model)' }),
2777
2595
  ]);
2778
2596
  mockIssueRepository.getOpenPullRequest.mockResolvedValue({
2779
2597
  url: prUrl,
@@ -3204,7 +3022,7 @@ describe('NotifyFinishedIssuePreparationUseCase', () => {
3204
3022
  mockProjectRepository.getByUrl.mockResolvedValue(mockProject);
3205
3023
  mockIssueRepository.get.mockResolvedValue(prIssue);
3206
3024
  mockIssueCommentRepository.getCommentsFromIssue.mockResolvedValue([
3207
- createMockComment({ content: 'From: :robot: Agent report' }),
3025
+ createMockComment({ content: 'From: :robot: agent (model)' }),
3208
3026
  ]);
3209
3027
  mockIssueRepository.getOpenPullRequest.mockResolvedValue(null);
3210
3028
 
@@ -3239,7 +3057,7 @@ describe('NotifyFinishedIssuePreparationUseCase', () => {
3239
3057
  mockIssueRepository.get.mockResolvedValue(issue);
3240
3058
  mockIssueCommentRepository.getCommentsFromIssue.mockResolvedValue([
3241
3059
  createMockComment({
3242
- content: 'From: :robot: Agent report\n```json\n{invalid json}\n```',
3060
+ content: 'From: :robot: agent (model)\n```json\n{invalid json}\n```',
3243
3061
  }),
3244
3062
  ]);
3245
3063
  mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([
@@ -3278,7 +3096,7 @@ describe('NotifyFinishedIssuePreparationUseCase', () => {
3278
3096
  mockIssueRepository.get.mockResolvedValue(issue);
3279
3097
  mockIssueCommentRepository.getCommentsFromIssue.mockResolvedValue([
3280
3098
  createMockComment({
3281
- content: 'From: :robot: Agent report\n```json\nnull\n```',
3099
+ content: 'From: :robot: agent (model)\n```json\nnull\n```',
3282
3100
  }),
3283
3101
  ]);
3284
3102
  mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([
@@ -3318,7 +3136,7 @@ describe('NotifyFinishedIssuePreparationUseCase', () => {
3318
3136
  mockIssueCommentRepository.getCommentsFromIssue.mockResolvedValue([
3319
3137
  createMockComment({
3320
3138
  content:
3321
- 'From: :robot: Agent report\n```json\n{"status": "done", "result": "success"}\n```',
3139
+ 'From: :robot: agent (model)\n```json\n{"status": "done", "result": "success"}\n```',
3322
3140
  }),
3323
3141
  ]);
3324
3142
  mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([
@@ -3357,7 +3175,7 @@ describe('NotifyFinishedIssuePreparationUseCase', () => {
3357
3175
  mockIssueRepository.get.mockResolvedValue(issue);
3358
3176
  mockIssueCommentRepository.getCommentsFromIssue.mockResolvedValue([
3359
3177
  createMockComment({
3360
- content: 'From: :robot: Agent report\n```json\n"just a string"\n```',
3178
+ content: 'From: :robot: agent (model)\n```json\n"just a string"\n```',
3361
3179
  }),
3362
3180
  ]);
3363
3181
  mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([
@@ -3686,7 +3504,7 @@ describe('NotifyFinishedIssuePreparationUseCase', () => {
3686
3504
  mockProjectRepository.getByUrl.mockResolvedValue(mockProject);
3687
3505
  mockIssueRepository.get.mockResolvedValue(issue);
3688
3506
  mockIssueCommentRepository.getCommentsFromIssue.mockResolvedValue([
3689
- createMockComment({ content: 'From: :robot: Agent report' }),
3507
+ createMockComment({ content: 'From: :robot: agent (model)' }),
3690
3508
  ]);
3691
3509
  mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([
3692
3510
  {
@@ -3883,7 +3701,7 @@ describe('NotifyFinishedIssuePreparationUseCase', () => {
3883
3701
  mockProjectRepository.getByUrl.mockResolvedValue(mockProject);
3884
3702
  mockIssueRepository.get.mockResolvedValue(issue);
3885
3703
  mockIssueCommentRepository.getCommentsFromIssue.mockResolvedValue([
3886
- createMockComment({ content: 'From: :robot: Agent report' }),
3704
+ createMockComment({ content: 'From: :robot: agent (model)' }),
3887
3705
  ]);
3888
3706
  mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([]);
3889
3707
 
@@ -4002,7 +3820,7 @@ describe('NotifyFinishedIssuePreparationUseCase', () => {
4002
3820
  mockIssueCommentRepository.getCommentsFromIssue.mockResolvedValue([
4003
3821
  createMockComment({
4004
3822
  content:
4005
- 'From: :robot: agent (model)\n```json\n{"nextStep": "do something", "nextStepAgent": "impl"}\n```',
3823
+ 'From: :robot: agent (model)\n```json\n{"nextStep": null, "nextStepAgent": "impl"}\n```',
4006
3824
  }),
4007
3825
  ]);
4008
3826
  mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([]);
@@ -4030,7 +3848,7 @@ describe('NotifyFinishedIssuePreparationUseCase', () => {
4030
3848
  mockIssueCommentRepository.getCommentsFromIssue.mockResolvedValue([
4031
3849
  createMockComment({
4032
3850
  content:
4033
- 'From: :robot: agent (model)\n```json\n{"nextStep": "do something", "nextStepAgent": "impl"}\n```',
3851
+ 'From: :robot: agent (model)\n```json\n{"nextStep": null, "nextStepAgent": "impl"}\n```',
4034
3852
  }),
4035
3853
  createMockComment({
4036
3854
  content:
@@ -4058,7 +3876,7 @@ describe('NotifyFinishedIssuePreparationUseCase', () => {
4058
3876
  mockIssueCommentRepository.getCommentsFromIssue.mockResolvedValue([
4059
3877
  createMockComment({
4060
3878
  content:
4061
- 'From: :robot: agent (model)\n```json\n{"nextStep": "review", "nextStepAgent": "chore"}\n```',
3879
+ 'From: :robot: agent (model)\n```json\n{"nextStep": null, "nextStepAgent": "chore"}\n```',
4062
3880
  }),
4063
3881
  ]);
4064
3882
  mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([]);
@@ -4132,7 +3950,7 @@ describe('NotifyFinishedIssuePreparationUseCase', () => {
4132
3950
  mockIssueCommentRepository.getCommentsFromIssue.mockResolvedValue([
4133
3951
  createMockComment({
4134
3952
  content:
4135
- 'From: :robot: agent (model)\n```json\n{"nextStep": "go", "nextStepAgent": "impl"}\n```',
3953
+ 'From: :robot: agent (model)\n```json\n{"nextStep": null, "nextStepAgent": "impl"}\n```',
4136
3954
  }),
4137
3955
  ]);
4138
3956
  mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([]);
@@ -4847,73 +4665,6 @@ describe('NotifyFinishedIssuePreparationUseCase', () => {
4847
4665
  describe('waitingForOwnerApproval', () => {
4848
4666
  const issueUrl = 'https://github.com/user/repo/issues/1';
4849
4667
 
4850
- it('moves to Awaiting Quality Check and posts AWAITING_OWNER_APPROVAL message when last report declares waitingForOwnerApproval', async () => {
4851
- const issue = createMockIssue({ url: issueUrl, status: 'Preparation' });
4852
- mockProjectRepository.getByUrl.mockResolvedValue(mockProject);
4853
- mockIssueRepository.get.mockResolvedValue(issue);
4854
- mockIssueCommentRepository.getCommentsFromIssue.mockResolvedValue([
4855
- createMockComment({
4856
- content:
4857
- 'From: :robot: systems-analyst (model)\n```json\n{"pullRequestRequired": false, "waitingForOwnerApproval": true}\n```',
4858
- }),
4859
- ]);
4860
- mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([]);
4861
-
4862
- await useCase.run({
4863
- projectUrl: 'https://github.com/users/user/projects/1',
4864
- issueUrl,
4865
- thresholdForAutoReject: 3,
4866
- workflowBlockerResolvedWebhookUrl: null,
4867
- allowedIssueAuthors: ['test-user'],
4868
- });
4869
-
4870
- expect(mockIssueRepository.update).toHaveBeenCalledWith(
4871
- expect.objectContaining({ status: 'Awaiting Quality Check' }),
4872
- mockProject,
4873
- );
4874
- expect(mockIssueRepository.updateStatus).toHaveBeenCalledWith(
4875
- mockProject,
4876
- expect.objectContaining({ status: 'Awaiting Quality Check' }),
4877
- 'awaiting-quality-check-id',
4878
- );
4879
- expect(mockIssueCommentRepository.createComment).toHaveBeenCalledWith(
4880
- expect.objectContaining({ url: issueUrl }),
4881
- expect.stringContaining('Auto Status Check: AWAITING_OWNER_APPROVAL'),
4882
- );
4883
- expect(mockIssueRepository.updateStatus).not.toHaveBeenCalledWith(
4884
- mockProject,
4885
- expect.anything(),
4886
- 'failed-preparation-id',
4887
- );
4888
- });
4889
-
4890
- it('does not return the issue to the dispatch pool when it is waiting for owner approval', async () => {
4891
- const issue = createMockIssue({ url: issueUrl, status: 'Preparation' });
4892
- mockProjectRepository.getByUrl.mockResolvedValue(mockProject);
4893
- mockIssueRepository.get.mockResolvedValue(issue);
4894
- mockIssueCommentRepository.getCommentsFromIssue.mockResolvedValue([
4895
- createMockComment({
4896
- content:
4897
- 'From: :robot: systems-analyst (model)\n```json\n{"pullRequestRequired": false, "waitingForOwnerApproval": true}\n```',
4898
- }),
4899
- ]);
4900
- mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([]);
4901
-
4902
- await useCase.run({
4903
- projectUrl: 'https://github.com/users/user/projects/1',
4904
- issueUrl,
4905
- thresholdForAutoReject: 3,
4906
- workflowBlockerResolvedWebhookUrl: null,
4907
- allowedIssueAuthors: ['test-user'],
4908
- });
4909
-
4910
- expect(mockIssueRepository.updateStatus).not.toHaveBeenCalledWith(
4911
- mockProject,
4912
- expect.anything(),
4913
- 'awaiting-workspace-id',
4914
- );
4915
- });
4916
-
4917
4668
  it('writes no status at all when the project has no Awaiting Quality Check option', async () => {
4918
4669
  const projectWithoutQualityCheck = {
4919
4670
  ...mockProject,
@@ -4947,48 +4698,6 @@ describe('NotifyFinishedIssuePreparationUseCase', () => {
4947
4698
 
4948
4699
  expect(mockIssueRepository.updateStatus.mock.calls).toHaveLength(0);
4949
4700
  });
4950
-
4951
- it('escalates to Failed Preparation after ownerApprovalTimeoutCycles AWAITING_OWNER_APPROVAL messages', async () => {
4952
- const issue = createMockIssue({ url: issueUrl, status: 'Preparation' });
4953
- mockProjectRepository.getByUrl.mockResolvedValue(mockProject);
4954
- mockIssueRepository.get.mockResolvedValue(issue);
4955
- mockIssueCommentRepository.getCommentsFromIssue.mockResolvedValue([
4956
- createMockComment({
4957
- content:
4958
- 'From: :robot: systems-analyst (model)\n```json\n{"pullRequestRequired": false, "waitingForOwnerApproval": true}\n```',
4959
- }),
4960
- createMockComment({
4961
- content:
4962
- 'Auto Status Check: AWAITING_OWNER_APPROVAL\nThe last report declared that this task is waiting for owner approval. Returning to Awaiting Workspace for the next cycle.',
4963
- }),
4964
- createMockComment({
4965
- content:
4966
- 'Auto Status Check: AWAITING_OWNER_APPROVAL\nThe last report declared that this task is waiting for owner approval. Returning to Awaiting Workspace for the next cycle.',
4967
- }),
4968
- ]);
4969
- mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([]);
4970
-
4971
- await useCase.run({
4972
- projectUrl: 'https://github.com/users/user/projects/1',
4973
- issueUrl,
4974
- thresholdForAutoReject: 3,
4975
- workflowBlockerResolvedWebhookUrl: null,
4976
- allowedIssueAuthors: ['test-user'],
4977
- ownerApprovalTimeoutCycles: 2,
4978
- });
4979
-
4980
- expect(mockIssueRepository.updateStatus).toHaveBeenCalledWith(
4981
- mockProject,
4982
- expect.anything(),
4983
- 'failed-preparation-id',
4984
- );
4985
- expect(mockIssueCommentRepository.createComment).toHaveBeenCalledWith(
4986
- expect.anything(),
4987
- expect.stringContaining(
4988
- 'Owner approval was not received after 2 cycles. Moving to Failed Preparation.',
4989
- ),
4990
- );
4991
- });
4992
4701
  });
4993
4702
 
4994
4703
  describe('non-developer agent CI failure reassignment', () => {
@@ -5336,4 +5045,153 @@ describe('NotifyFinishedIssuePreparationUseCase', () => {
5336
5045
  expect(mockIssueRepository.setIssueAgentField).not.toHaveBeenCalled();
5337
5046
  });
5338
5047
  });
5048
+ describe('routing a finished preparation by the designated next step agent', () => {
5049
+ const projectWithAgent = () =>
5050
+ createMockProject({
5051
+ agent: {
5052
+ name: AGENT_FIELD_NAME,
5053
+ fieldId: 'agent-field-id',
5054
+ options: [
5055
+ { id: 'opt-impl', name: 'impl', color: 'GRAY', description: '' },
5056
+ ],
5057
+ },
5058
+ });
5059
+
5060
+ it('should move the issue to Awaiting Quality Check without touching the agent field when no next step agent is designated', async () => {
5061
+ const issue = createMockIssue({ status: 'Preparation' });
5062
+ mockProjectRepository.getByUrl.mockResolvedValue(projectWithAgent());
5063
+ mockIssueRepository.get.mockResolvedValue(issue);
5064
+ mockIssueCommentRepository.getCommentsFromIssue.mockResolvedValue([
5065
+ createMockComment({ content: 'From: :robot: agent (model)' }),
5066
+ ]);
5067
+ mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([
5068
+ {
5069
+ url: 'https://github.com/user/repo/pull/1',
5070
+ isConflicted: false,
5071
+ isPassedAllCiJob: true,
5072
+ isCiStateSuccess: true,
5073
+ isResolvedAllReviewComments: true,
5074
+ isBranchOutOfDate: false,
5075
+ missingRequiredCheckNames: [],
5076
+ },
5077
+ ]);
5078
+
5079
+ await useCase.run({
5080
+ projectUrl: 'https://github.com/users/user/projects/1',
5081
+ issueUrl: 'https://github.com/user/repo/issues/1',
5082
+ thresholdForAutoReject: 3,
5083
+ workflowBlockerResolvedWebhookUrl: null,
5084
+ allowedIssueAuthors: ['test-user'],
5085
+ });
5086
+
5087
+ expect(mockIssueRepository.update).toHaveBeenCalledWith(
5088
+ expect.objectContaining({ status: 'Awaiting Quality Check' }),
5089
+ expect.anything(),
5090
+ );
5091
+ expect(mockIssueRepository.setIssueAgentField).not.toHaveBeenCalled();
5092
+ });
5093
+
5094
+ it('should set the agent field and return the issue to Awaiting Workspace when a next step agent is designated', async () => {
5095
+ const project = projectWithAgent();
5096
+ const issue = createMockIssue({ status: 'Preparation' });
5097
+ mockProjectRepository.getByUrl.mockResolvedValue(project);
5098
+ mockIssueRepository.get.mockResolvedValue(issue);
5099
+ mockIssueCommentRepository.getCommentsFromIssue.mockResolvedValue([
5100
+ createMockComment({
5101
+ content:
5102
+ 'From: :robot: agent (model)\n```json\n{"nextStepAgent": "impl"}\n```',
5103
+ }),
5104
+ ]);
5105
+ mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([
5106
+ {
5107
+ url: 'https://github.com/user/repo/pull/1',
5108
+ isConflicted: false,
5109
+ isPassedAllCiJob: true,
5110
+ isCiStateSuccess: true,
5111
+ isResolvedAllReviewComments: true,
5112
+ isBranchOutOfDate: false,
5113
+ missingRequiredCheckNames: [],
5114
+ },
5115
+ ]);
5116
+
5117
+ await useCase.run({
5118
+ projectUrl: 'https://github.com/users/user/projects/1',
5119
+ issueUrl: 'https://github.com/user/repo/issues/1',
5120
+ thresholdForAutoReject: 3,
5121
+ workflowBlockerResolvedWebhookUrl: null,
5122
+ allowedIssueAuthors: ['test-user'],
5123
+ });
5124
+
5125
+ expect(mockIssueRepository.setIssueAgentField).toHaveBeenCalledWith(
5126
+ 'https://github.com/user/repo/issues/1',
5127
+ project,
5128
+ 'opt-impl',
5129
+ );
5130
+ expect(mockIssueRepository.update).toHaveBeenCalledWith(
5131
+ expect.objectContaining({ status: 'Awaiting Workspace' }),
5132
+ project,
5133
+ );
5134
+ });
5135
+
5136
+ it('should waive the missing pull request rejection when the designated next step agent is the triager', async () => {
5137
+ const issue = createMockIssue({ status: 'Preparation' });
5138
+ mockProjectRepository.getByUrl.mockResolvedValue(projectWithAgent());
5139
+ mockIssueRepository.get.mockResolvedValue(issue);
5140
+ mockIssueCommentRepository.getCommentsFromIssue.mockResolvedValue([
5141
+ createMockComment({
5142
+ content:
5143
+ 'From: :robot: agent (model)\n```json\n{"nextStepAgent": "triager"}\n```',
5144
+ }),
5145
+ ]);
5146
+ mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([]);
5147
+
5148
+ await useCase.run({
5149
+ projectUrl: 'https://github.com/users/user/projects/1',
5150
+ issueUrl: 'https://github.com/user/repo/issues/1',
5151
+ thresholdForAutoReject: 3,
5152
+ workflowBlockerResolvedWebhookUrl: null,
5153
+ allowedIssueAuthors: ['test-user'],
5154
+ });
5155
+
5156
+ expect(mockIssueCommentRepository.createComment).not.toHaveBeenCalledWith(
5157
+ expect.anything(),
5158
+ expect.stringContaining('PULL_REQUEST_NOT_FOUND'),
5159
+ );
5160
+ expect(mockIssueRepository.update).toHaveBeenCalledWith(
5161
+ expect.objectContaining({ status: 'Awaiting Workspace' }),
5162
+ expect.anything(),
5163
+ );
5164
+ });
5165
+
5166
+ it('should keep the missing pull request rejection when the designated next step agent is not the triager', async () => {
5167
+ const issue = createMockIssue({ status: 'Preparation' });
5168
+ mockProjectRepository.getByUrl.mockResolvedValue(projectWithAgent());
5169
+ mockIssueRepository.get.mockResolvedValue(issue);
5170
+ mockIssueCommentRepository.getCommentsFromIssue.mockResolvedValue([
5171
+ createMockComment({
5172
+ content:
5173
+ 'From: :robot: agent (model)\n```json\n{"nextStepAgent": "impl"}\n```',
5174
+ }),
5175
+ ]);
5176
+ mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([]);
5177
+
5178
+ await useCase.run({
5179
+ projectUrl: 'https://github.com/users/user/projects/1',
5180
+ issueUrl: 'https://github.com/user/repo/issues/1',
5181
+ thresholdForAutoReject: 3,
5182
+ workflowBlockerResolvedWebhookUrl: null,
5183
+ allowedIssueAuthors: ['test-user'],
5184
+ });
5185
+
5186
+ expect(mockIssueCommentRepository.createComment).toHaveBeenCalledWith(
5187
+ expect.anything(),
5188
+ expect.stringContaining('PULL_REQUEST_NOT_FOUND'),
5189
+ );
5190
+ expect(mockIssueRepository.updateStatus).toHaveBeenCalledWith(
5191
+ expect.anything(),
5192
+ expect.anything(),
5193
+ 'awaiting-workspace-id',
5194
+ );
5195
+ });
5196
+ });
5339
5197
  });