github-issue-tower-defence-management 1.174.2 → 1.175.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 (82) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/bin/adapter/entry-points/console/consoleDoneStore.js +0 -2
  3. package/bin/adapter/entry-points/console/consoleDoneStore.js.map +1 -1
  4. package/bin/adapter/entry-points/console/consoleTabNames.js +0 -1
  5. package/bin/adapter/entry-points/console/consoleTabNames.js.map +1 -1
  6. package/bin/adapter/entry-points/console/dashboardComposeService.js +2 -4
  7. package/bin/adapter/entry-points/console/dashboardComposeService.js.map +1 -1
  8. package/bin/adapter/entry-points/console/ui-dist/assets/{index-g4SqIzCh.js → index-BhDothvr.js} +1 -1
  9. package/bin/adapter/entry-points/console/ui-dist/index.html +1 -1
  10. package/bin/domain/entities/WorkflowStatus.js +1 -6
  11. package/bin/domain/entities/WorkflowStatus.js.map +1 -1
  12. package/bin/domain/usecases/NotifyFinishedIssuePreparationUseCase.js +4 -5
  13. package/bin/domain/usecases/NotifyFinishedIssuePreparationUseCase.js.map +1 -1
  14. package/bin/domain/usecases/RevertNotReadyReviewQueueIssueUseCase.js +0 -39
  15. package/bin/domain/usecases/RevertNotReadyReviewQueueIssueUseCase.js.map +1 -1
  16. package/bin/domain/usecases/RevertOrphanedPreparationUseCase.js +4 -5
  17. package/bin/domain/usecases/RevertOrphanedPreparationUseCase.js.map +1 -1
  18. package/bin/domain/usecases/SetupTowerDefenceProjectUseCase.js +15 -1
  19. package/bin/domain/usecases/SetupTowerDefenceProjectUseCase.js.map +1 -1
  20. package/bin/domain/usecases/console/GenerateConsoleListsUseCase.js +0 -2
  21. package/bin/domain/usecases/console/GenerateConsoleListsUseCase.js.map +1 -1
  22. package/bin/domain/usecases/dashboard/ComposeDashboardUseCase.js +2 -3
  23. package/bin/domain/usecases/dashboard/ComposeDashboardUseCase.js.map +1 -1
  24. package/bin/domain/usecases/dashboard/GenerateDashboardRowUseCase.js +0 -1
  25. package/bin/domain/usecases/dashboard/GenerateDashboardRowUseCase.js.map +1 -1
  26. package/bin/domain/usecases/findLastAgentDeclaringReport.js +12 -0
  27. package/bin/domain/usecases/findLastAgentDeclaringReport.js.map +1 -0
  28. package/package.json +1 -1
  29. package/src/adapter/entry-points/console/consoleDoneStore.ts +0 -2
  30. package/src/adapter/entry-points/console/consoleTabNames.ts +0 -1
  31. package/src/adapter/entry-points/console/dashboardComposeService.test.ts +4 -8
  32. package/src/adapter/entry-points/console/dashboardComposeService.ts +1 -3
  33. package/src/adapter/entry-points/console/dashboardEmitterComposerKey.test.ts +12 -17
  34. package/src/adapter/entry-points/console/ui/e2e/consoleScenario.spec.ts +4 -32
  35. package/src/adapter/entry-points/console/ui/e2e/consoleTestHarness.ts +1 -18
  36. package/src/adapter/entry-points/console/ui/src/features/console/components/layout/ConsoleTabList.stories.tsx +3 -7
  37. package/src/adapter/entry-points/console/ui/src/features/console/components/layout/ConsoleTabList.test.tsx +2 -4
  38. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleOperationMenu.test.tsx +1 -1
  39. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleNavigation.test.ts +6 -8
  40. package/src/adapter/entry-points/console/ui/src/features/console/logic/tabAdvance.test.ts +8 -17
  41. package/src/adapter/entry-points/console/ui/src/features/console/logic/types.ts +0 -2
  42. package/src/adapter/entry-points/console/ui/src/features/console/pages/ConsoleItemDetailContainer.test.tsx +3 -3
  43. package/src/adapter/entry-points/console/ui/src/features/console/pages/ConsolePage.test.tsx +5 -6
  44. package/src/adapter/entry-points/console/ui-dist/assets/{index-g4SqIzCh.js → index-BhDothvr.js} +1 -1
  45. package/src/adapter/entry-points/console/ui-dist/index.html +1 -1
  46. package/src/adapter/entry-points/console/webServer.test.ts +4 -6
  47. package/src/adapter/entry-points/handlers/consoleListsWriter.test.ts +30 -26
  48. package/src/adapter/entry-points/handlers/dashboardRowWriter.test.ts +5 -6
  49. package/src/domain/entities/WorkflowStatus.ts +0 -5
  50. package/src/domain/usecases/NotifyFinishedIssuePreparationUseCase.test.ts +32 -0
  51. package/src/domain/usecases/NotifyFinishedIssuePreparationUseCase.ts +7 -8
  52. package/src/domain/usecases/RevertNotReadyReviewQueueIssueUseCase.test.ts +198 -863
  53. package/src/domain/usecases/RevertNotReadyReviewQueueIssueUseCase.ts +0 -68
  54. package/src/domain/usecases/RevertOrphanedPreparationUseCase.ts +8 -11
  55. package/src/domain/usecases/SetupTowerDefenceProjectUseCase.test.ts +89 -51
  56. package/src/domain/usecases/SetupTowerDefenceProjectUseCase.ts +30 -2
  57. package/src/domain/usecases/console/GenerateConsoleListsUseCase.test.ts +62 -72
  58. package/src/domain/usecases/console/GenerateConsoleListsUseCase.ts +0 -8
  59. package/src/domain/usecases/dashboard/ComposeDashboardUseCase.test.ts +19 -28
  60. package/src/domain/usecases/dashboard/ComposeDashboardUseCase.ts +2 -3
  61. package/src/domain/usecases/dashboard/GenerateDashboardRowUseCase.test.ts +11 -12
  62. package/src/domain/usecases/dashboard/GenerateDashboardRowUseCase.ts +0 -3
  63. package/src/domain/usecases/findLastAgentDeclaringReport.test.ts +71 -0
  64. package/src/domain/usecases/findLastAgentDeclaringReport.ts +17 -0
  65. package/src/domain/usecases/intmux/GenerateInTmuxByHumanDataUseCase.test.ts +2 -2
  66. package/types/adapter/entry-points/console/consoleDoneStore.d.ts.map +1 -1
  67. package/types/adapter/entry-points/console/consoleTabNames.d.ts.map +1 -1
  68. package/types/adapter/entry-points/console/dashboardComposeService.d.ts.map +1 -1
  69. package/types/domain/entities/WorkflowStatus.d.ts +0 -1
  70. package/types/domain/entities/WorkflowStatus.d.ts.map +1 -1
  71. package/types/domain/usecases/NotifyFinishedIssuePreparationUseCase.d.ts.map +1 -1
  72. package/types/domain/usecases/RevertNotReadyReviewQueueIssueUseCase.d.ts.map +1 -1
  73. package/types/domain/usecases/RevertOrphanedPreparationUseCase.d.ts.map +1 -1
  74. package/types/domain/usecases/SetupTowerDefenceProjectUseCase.d.ts +1 -0
  75. package/types/domain/usecases/SetupTowerDefenceProjectUseCase.d.ts.map +1 -1
  76. package/types/domain/usecases/console/GenerateConsoleListsUseCase.d.ts +1 -2
  77. package/types/domain/usecases/console/GenerateConsoleListsUseCase.d.ts.map +1 -1
  78. package/types/domain/usecases/dashboard/ComposeDashboardUseCase.d.ts.map +1 -1
  79. package/types/domain/usecases/dashboard/GenerateDashboardRowUseCase.d.ts +0 -1
  80. package/types/domain/usecases/dashboard/GenerateDashboardRowUseCase.d.ts.map +1 -1
  81. package/types/domain/usecases/findLastAgentDeclaringReport.d.ts +5 -0
  82. package/types/domain/usecases/findLastAgentDeclaringReport.d.ts.map +1 -0
@@ -4,7 +4,7 @@
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <title>TDPM Console</title>
7
- <script type="module" crossorigin src="/assets/index-g4SqIzCh.js"></script>
7
+ <script type="module" crossorigin src="/assets/index-BhDothvr.js"></script>
8
8
  <link rel="stylesheet" crossorigin href="/assets/index-Bh2slMtk.css">
9
9
  </head>
10
10
  <body>
@@ -83,7 +83,7 @@ describe('webServer pure helpers', () => {
83
83
  expect(isConsoleAppRoute('/projects/acme/workflow-blocker')).toBe(true);
84
84
  expect(isConsoleAppRoute('/projects/acme/prs')).toBe(true);
85
85
  expect(isConsoleAppRoute('/projects/globex/triage')).toBe(true);
86
- expect(isConsoleAppRoute('/projects/initech/unread')).toBe(true);
86
+ expect(isConsoleAppRoute('/projects/initech/todo-by-human')).toBe(true);
87
87
  expect(isConsoleAppRoute('/projects/umbrella/failed-preparation')).toBe(
88
88
  true,
89
89
  );
@@ -1473,7 +1473,6 @@ describe('webServer dashboard /tdpm.txt route integration', () => {
1473
1473
  JSON.stringify({
1474
1474
  pjcode: 'acme',
1475
1475
  capturedAt: '2026-06-26T00:00:00.000Z',
1476
- unread: 3,
1477
1476
  todo: 1,
1478
1477
  qc: 2,
1479
1478
  fail: 0,
@@ -1488,7 +1487,6 @@ describe('webServer dashboard /tdpm.txt route integration', () => {
1488
1487
  JSON.stringify({
1489
1488
  pjcode: 'initech',
1490
1489
  capturedAt: '2026-06-26T00:00:00.000Z',
1491
- unread: 0,
1492
1490
  todo: 0,
1493
1491
  qc: 0,
1494
1492
  fail: 0,
@@ -1532,9 +1530,9 @@ describe('webServer dashboard /tdpm.txt route integration', () => {
1532
1530
  const expectedComposed =
1533
1531
  '<tt>M55%&nbsp;C62%&nbsp;D89%&nbsp;cy14</tt><br>\n' +
1534
1532
  '<tt>LA&nbsp;16&nbsp;23&nbsp;40</tt><br>\n' +
1535
- '<tt>pj&nbsp;&nbsp;&nbsp;unr&nbsp;tdo&nbsp;aqc&nbsp;fal&nbsp;prp&nbsp;aws&nbsp;dep</tt><br>\n' +
1536
- '<tt>🟢ac&nbsp;&nbsp;&nbsp;3&nbsp;&nbsp;&nbsp;1&nbsp;&nbsp;&nbsp;2&nbsp;&nbsp;&nbsp;0&nbsp;&nbsp;&nbsp;0&nbsp;&nbsp;&nbsp;4&nbsp;&nbsp;&nbsp;1</tt><br>\n' +
1537
- '<tt>🟢in&nbsp;&nbsp;&nbsp;0&nbsp;&nbsp;&nbsp;0&nbsp;&nbsp;&nbsp;0&nbsp;&nbsp;&nbsp;0&nbsp;&nbsp;&nbsp;0&nbsp;&nbsp;&nbsp;2&nbsp;&nbsp;&nbsp;0</tt><br>\n' +
1533
+ '<tt>pj&nbsp;&nbsp;&nbsp;tdo&nbsp;aqc&nbsp;fal&nbsp;prp&nbsp;aws&nbsp;dep</tt><br>\n' +
1534
+ '<tt>🟢ac&nbsp;&nbsp;&nbsp;1&nbsp;&nbsp;&nbsp;2&nbsp;&nbsp;&nbsp;0&nbsp;&nbsp;&nbsp;0&nbsp;&nbsp;&nbsp;4&nbsp;&nbsp;&nbsp;1</tt><br>\n' +
1535
+ '<tt>🟢in&nbsp;&nbsp;&nbsp;0&nbsp;&nbsp;&nbsp;0&nbsp;&nbsp;&nbsp;0&nbsp;&nbsp;&nbsp;0&nbsp;&nbsp;&nbsp;2&nbsp;&nbsp;&nbsp;0</tt><br>\n' +
1538
1536
  '<tt></tt><br>\n' +
1539
1537
  '<tt>🟢alice&nbsp;&nbsp;10%&nbsp;0d01h00&nbsp;&nbsp;12%&nbsp;5d00h00&nbsp;2&nbsp;1</tt><br>\n';
1540
1538
 
@@ -38,7 +38,6 @@ const project: Project = {
38
38
  name: 'Status',
39
39
  fieldId: 'status-field',
40
40
  statuses: [
41
- option('st-unread', 'Unread', 'ORANGE'),
42
41
  option('st-aw', 'Awaiting Workspace', 'BLUE'),
43
42
  option('st-aqc', 'Awaiting Quality Check', 'GREEN'),
44
43
  ],
@@ -93,7 +92,7 @@ describe('writeConsoleLists', () => {
93
92
  pjcode: 'demo',
94
93
  assigneeLogin: ASSIGNEE,
95
94
  project,
96
- issues: [makeIssue({ status: 'Unread' })],
95
+ issues: [makeIssue({ status: 'Todo by human' })],
97
96
  generatedAt: '2026-06-14T07:22:33Z',
98
97
  });
99
98
 
@@ -101,7 +100,6 @@ describe('writeConsoleLists', () => {
101
100
  'workflow-blocker',
102
101
  'prs',
103
102
  'triage',
104
- 'unread',
105
103
  'failed-preparation',
106
104
  'todo-by-human',
107
105
  'todo-by-agent',
@@ -198,11 +196,13 @@ describe('writeConsoleLists', () => {
198
196
  pjcode: 'demo',
199
197
  assigneeLogin: ASSIGNEE,
200
198
  project,
201
- issues: [makeIssue({ status: 'Unread' })],
199
+ issues: [makeIssue({ status: 'Todo by human' })],
202
200
  generatedAt: '2026-06-14T07:22:33Z',
203
201
  });
204
202
 
205
- const raw: unknown = JSON.parse(fs.readFileSync(tabFile('unread'), 'utf8'));
203
+ const raw: unknown = JSON.parse(
204
+ fs.readFileSync(tabFile('todo-by-human'), 'utf8'),
205
+ );
206
206
  expect(isRecord(raw)).toBe(true);
207
207
  const items: unknown = isRecord(raw) ? raw.items : undefined;
208
208
  expect(isUnknownArray(items)).toBe(true);
@@ -229,7 +229,7 @@ describe('writeConsoleLists', () => {
229
229
  pjcode: 'demo',
230
230
  assigneeLogin: ASSIGNEE,
231
231
  project,
232
- issues: [makeIssue({ status: 'Unread' })],
232
+ issues: [makeIssue({ status: 'Todo by human' })],
233
233
  });
234
234
  expect(fs.readdirSync(outDir)).toHaveLength(0);
235
235
  });
@@ -240,7 +240,7 @@ describe('writeConsoleLists', () => {
240
240
  pjcode: '',
241
241
  assigneeLogin: ASSIGNEE,
242
242
  project,
243
- issues: [makeIssue({ status: 'Unread' })],
243
+ issues: [makeIssue({ status: 'Todo by human' })],
244
244
  });
245
245
  expect(fs.readdirSync(outDir)).toHaveLength(0);
246
246
  });
@@ -251,24 +251,26 @@ describe('writeConsoleLists', () => {
251
251
  pjcode: 'demo',
252
252
  assigneeLogin: null,
253
253
  project,
254
- issues: [makeIssue({ status: 'Unread' })],
254
+ issues: [makeIssue({ status: 'Todo by human' })],
255
255
  });
256
256
  expect(fs.readdirSync(outDir)).toHaveLength(0);
257
257
  });
258
258
 
259
- const regenerateUnread = (): void => {
259
+ const regenerateTodoByHuman = (): void => {
260
260
  writeConsoleLists({
261
261
  consoleDataOutputDir: outDir,
262
262
  pjcode: 'demo',
263
263
  assigneeLogin: ASSIGNEE,
264
264
  project,
265
- issues: [makeIssue({ status: 'Unread', itemId: 'item-1' })],
265
+ issues: [makeIssue({ status: 'Todo by human', itemId: 'item-1' })],
266
266
  generatedAt: '2026-06-14T07:22:33Z',
267
267
  });
268
268
  };
269
269
 
270
- const unreadServedItemCount = (): number => {
271
- const route = parseConsoleDataRoute('projects/demo/unread/list.json');
270
+ const todoByHumanServedItemCount = (): number => {
271
+ const route = parseConsoleDataRoute(
272
+ 'projects/demo/todo-by-human/list.json',
273
+ );
272
274
  if (route === null) {
273
275
  throw new Error('route should not be null');
274
276
  }
@@ -279,7 +281,7 @@ describe('writeConsoleLists', () => {
279
281
  };
280
282
 
281
283
  it('resets the done file of every tab to an empty record on regeneration', () => {
282
- regenerateUnread();
284
+ regenerateTodoByHuman();
283
285
 
284
286
  for (const tab of CONSOLE_DONE_TAB_NAMES) {
285
287
  const doneFile = path.join(outDir, 'demo', tab, '.done.json');
@@ -291,32 +293,34 @@ describe('writeConsoleLists', () => {
291
293
 
292
294
  it('clears a pre-existing accumulated done file on regeneration', () => {
293
295
  for (const id of ['PVTI_1', 'PVTI_2', 'PVTI_3']) {
294
- recordDoneProjectItemId(outDir, 'demo', 'unread', id);
296
+ recordDoneProjectItemId(outDir, 'demo', 'todo-by-human', id);
295
297
  }
296
- expect(readDoneProjectItemIds(outDir, 'demo', 'unread')).toHaveLength(3);
298
+ expect(
299
+ readDoneProjectItemIds(outDir, 'demo', 'todo-by-human'),
300
+ ).toHaveLength(3);
297
301
 
298
- regenerateUnread();
302
+ regenerateTodoByHuman();
299
303
 
300
- expect(readDoneProjectItemIds(outDir, 'demo', 'unread')).toEqual([]);
304
+ expect(readDoneProjectItemIds(outDir, 'demo', 'todo-by-human')).toEqual([]);
301
305
  });
302
306
 
303
307
  it('serves the full list after regeneration despite a prior accumulated done record', () => {
304
- recordDoneProjectItemId(outDir, 'demo', 'unread', 'item-1');
308
+ recordDoneProjectItemId(outDir, 'demo', 'todo-by-human', 'item-1');
305
309
 
306
- regenerateUnread();
310
+ regenerateTodoByHuman();
307
311
 
308
- expect(unreadServedItemCount()).toBe(1);
312
+ expect(todoByHumanServedItemCount()).toBe(1);
309
313
  });
310
314
 
311
315
  it('re-hides an item recorded as done until the next regeneration bounds it to one cycle', () => {
312
- regenerateUnread();
313
- expect(unreadServedItemCount()).toBe(1);
316
+ regenerateTodoByHuman();
317
+ expect(todoByHumanServedItemCount()).toBe(1);
314
318
 
315
- recordDoneProjectItemId(outDir, 'demo', 'unread', 'item-1');
316
- expect(unreadServedItemCount()).toBe(0);
319
+ recordDoneProjectItemId(outDir, 'demo', 'todo-by-human', 'item-1');
320
+ expect(todoByHumanServedItemCount()).toBe(0);
317
321
 
318
- regenerateUnread();
319
- expect(unreadServedItemCount()).toBe(1);
322
+ regenerateTodoByHuman();
323
+ expect(todoByHumanServedItemCount()).toBe(1);
320
324
  });
321
325
  });
322
326
 
@@ -60,7 +60,7 @@ describe('writeDashboardRow', () => {
60
60
  pjcode: 'ac',
61
61
  assigneeLogin: ASSIGNEE,
62
62
  issues: [
63
- makeIssue({ status: 'Unread' }),
63
+ makeIssue({ status: 'Awaiting Workspace' }),
64
64
  makeIssue({ status: 'Awaiting Quality Check' }),
65
65
  ],
66
66
  generatedAt: '2026-06-26T12:00:00.000Z',
@@ -70,12 +70,11 @@ describe('writeDashboardRow', () => {
70
70
  const expected: DashboardRowFile = {
71
71
  pjcode: 'ac',
72
72
  capturedAt: '2026-06-26T12:00:00.000Z',
73
- unread: 1,
74
73
  todo: 0,
75
74
  qc: 1,
76
75
  fail: 0,
77
76
  pr: 0,
78
- ws: 0,
77
+ ws: 1,
79
78
  dep: 0,
80
79
  blocker: 0,
81
80
  };
@@ -87,7 +86,7 @@ describe('writeDashboardRow', () => {
87
86
  dashboardDataDir: null,
88
87
  pjcode: 'ac',
89
88
  assigneeLogin: ASSIGNEE,
90
- issues: [makeIssue({ status: 'Unread' })],
89
+ issues: [makeIssue({ status: 'Awaiting Workspace' })],
91
90
  });
92
91
 
93
92
  expect(fs.readdirSync(dir)).toEqual([]);
@@ -98,13 +97,13 @@ describe('writeDashboardRow', () => {
98
97
  dashboardDataDir: dir,
99
98
  pjcode: null,
100
99
  assigneeLogin: ASSIGNEE,
101
- issues: [makeIssue({ status: 'Unread' })],
100
+ issues: [makeIssue({ status: 'Awaiting Workspace' })],
102
101
  });
103
102
  writeDashboardRow({
104
103
  dashboardDataDir: dir,
105
104
  pjcode: 'ac',
106
105
  assigneeLogin: null,
107
- issues: [makeIssue({ status: 'Unread' })],
106
+ issues: [makeIssue({ status: 'Awaiting Workspace' })],
108
107
  });
109
108
 
110
109
  expect(fs.readdirSync(dir)).toEqual([]);
@@ -1,6 +1,5 @@
1
1
  import { FieldOption } from './Project';
2
2
 
3
- export const DEFAULT_STATUS_NAME = 'Unread';
4
3
  export const AWAITING_WORKSPACE_STATUS_NAME = 'Awaiting Workspace';
5
4
  export const PREPARATION_STATUS_NAME = 'Preparation';
6
5
  export const FAILED_PREPARATION_STATUS_NAME = 'Failed Preparation';
@@ -24,10 +23,6 @@ export type WorkflowStatusDefinition = {
24
23
  };
25
24
 
26
25
  export const REQUIRED_WORKFLOW_STATUSES: WorkflowStatusDefinition[] = [
27
- {
28
- name: DEFAULT_STATUS_NAME,
29
- color: 'ORANGE',
30
- },
31
26
  {
32
27
  name: AWAITING_WORKSPACE_STATUS_NAME,
33
28
  color: 'BLUE',
@@ -3728,6 +3728,38 @@ describe('NotifyFinishedIssuePreparationUseCase', () => {
3728
3728
  );
3729
3729
  });
3730
3730
 
3731
+ it('calls setIssueAgentField when a later bot comment carries no declaration of its own', async () => {
3732
+ const issue = createMockIssue({ status: 'Preparation' });
3733
+ const projectWithAgent = makeProjectWithAgent();
3734
+ mockProjectRepository.getByUrl.mockResolvedValue(projectWithAgent);
3735
+ mockIssueRepository.get.mockResolvedValue(issue);
3736
+ mockIssueCommentRepository.getCommentsFromIssue.mockResolvedValue([
3737
+ createMockComment({
3738
+ content:
3739
+ 'From: :robot: agent (model)\n```json\n{"nextStep": "do something", "nextStepAgent": "impl"}\n```',
3740
+ }),
3741
+ createMockComment({
3742
+ content:
3743
+ 'From: :robot: agent (model)\n\n## PR URL\nhttps://github.com/user/repo/pull/2\n',
3744
+ }),
3745
+ ]);
3746
+ mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([]);
3747
+
3748
+ await useCase.run({
3749
+ projectUrl: 'https://github.com/users/user/projects/1',
3750
+ issueUrl: 'https://github.com/user/repo/issues/1',
3751
+ thresholdForAutoReject: 3,
3752
+ workflowBlockerResolvedWebhookUrl: null,
3753
+ allowedIssueAuthors: null,
3754
+ });
3755
+
3756
+ expect(mockIssueRepository.setIssueAgentField).toHaveBeenCalledWith(
3757
+ 'https://github.com/user/repo/issues/1',
3758
+ projectWithAgent,
3759
+ 'opt-impl',
3760
+ );
3761
+ });
3762
+
3731
3763
  it('creates a new agent option and calls setIssueAgentField when nextStepAgent is not an existing option', async () => {
3732
3764
  const issue = createMockIssue({ status: 'Preparation' });
3733
3765
  const projectWithAgent = makeProjectWithAgent();
@@ -28,6 +28,7 @@ import { Issue } from '../entities/Issue';
28
28
  import { Project } from '../entities/Project';
29
29
  import { ensureAgentOptionAndGetId } from './ensureAgentOptionAndGetId';
30
30
  import { extractNextStepAgent } from './extractNextStepAgent';
31
+ import { findLastAgentDeclaringReport } from './findLastAgentDeclaringReport';
31
32
  import { issueReactivationTriggerIsPending } from './issueReactivationTriggerIsPending';
32
33
  import { normalizeReportBody } from './normalizeReportBody';
33
34
 
@@ -223,14 +224,12 @@ export class NotifyFinishedIssuePreparationUseCase {
223
224
  const isTrustedAuthor = (author: string): boolean =>
224
225
  this.isAuthorTrusted(author, params.allowedIssueAuthors ?? null);
225
226
 
226
- const lastTrustedBotComment = [...comments]
227
- .reverse()
228
- .find(
229
- (c) =>
230
- isTrustedAuthor(c.author) && c.content.startsWith('From: :robot:'),
231
- );
232
- const nextStepAgent = lastTrustedBotComment
233
- ? extractNextStepAgent(lastTrustedBotComment.content)
227
+ const lastAgentDeclaringReport = findLastAgentDeclaringReport(
228
+ comments,
229
+ isTrustedAuthor,
230
+ );
231
+ const nextStepAgent = lastAgentDeclaringReport
232
+ ? extractNextStepAgent(lastAgentDeclaringReport.content)
234
233
  : null;
235
234
  if (nextStepAgent !== null) {
236
235
  const agentOptionId = await this.ensureAgentOptionAndGetId(