github-issue-tower-defence-management 1.47.0 → 1.49.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.
- package/CHANGELOG.md +19 -0
- package/README.md +6 -25
- package/bin/adapter/entry-points/cli/index.js +2 -55
- package/bin/adapter/entry-points/cli/index.js.map +1 -1
- package/bin/adapter/entry-points/cli/projectConfig.js +0 -15
- package/bin/adapter/entry-points/cli/projectConfig.js.map +1 -1
- package/bin/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.js +12 -53
- package/bin/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.js.map +1 -1
- package/bin/adapter/repositories/GraphqlProjectRepository.js +37 -0
- package/bin/adapter/repositories/GraphqlProjectRepository.js.map +1 -1
- package/bin/domain/entities/WorkflowStatus.js +56 -0
- package/bin/domain/entities/WorkflowStatus.js.map +1 -0
- package/bin/domain/usecases/AnalyzeStoriesUseCase.js +2 -1
- package/bin/domain/usecases/AnalyzeStoriesUseCase.js.map +1 -1
- package/bin/domain/usecases/ChangeStatusByStoryColorUseCase.js +5 -4
- package/bin/domain/usecases/ChangeStatusByStoryColorUseCase.js.map +1 -1
- package/bin/domain/usecases/ConvertCheckboxToIssueInStoryIssueUseCase.js +2 -1
- package/bin/domain/usecases/ConvertCheckboxToIssueInStoryIssueUseCase.js.map +1 -1
- package/bin/domain/usecases/CreateEstimationIssueUseCase.js +2 -1
- package/bin/domain/usecases/CreateEstimationIssueUseCase.js.map +1 -1
- package/bin/domain/usecases/CreateNewStoryByLabelUseCase.js.map +1 -1
- package/bin/domain/usecases/HandleScheduledEventUseCase.js +9 -17
- package/bin/domain/usecases/HandleScheduledEventUseCase.js.map +1 -1
- package/bin/domain/usecases/NotifyFinishedIssuePreparationUseCase.js +13 -15
- package/bin/domain/usecases/NotifyFinishedIssuePreparationUseCase.js.map +1 -1
- package/bin/domain/usecases/RevertOrphanedPreparationUseCase.js +4 -5
- package/bin/domain/usecases/RevertOrphanedPreparationUseCase.js.map +1 -1
- package/bin/domain/usecases/SetupTowerDefenceProjectUseCase.js +49 -0
- package/bin/domain/usecases/SetupTowerDefenceProjectUseCase.js.map +1 -0
- package/bin/domain/usecases/StartPreparationUseCase.js +7 -8
- package/bin/domain/usecases/StartPreparationUseCase.js.map +1 -1
- package/bin/domain/usecases/UpdateIssueStatusByLabelUseCase.js.map +1 -1
- package/package.json +1 -1
- package/src/adapter/entry-points/cli/index.test.ts +8 -212
- package/src/adapter/entry-points/cli/index.ts +6 -88
- package/src/adapter/entry-points/cli/projectConfig.ts +0 -33
- package/src/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.test.ts +5 -17
- package/src/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.ts +16 -32
- package/src/adapter/repositories/GraphqlProjectRepository.ts +55 -1
- package/src/domain/entities/WorkflowStatus.ts +60 -0
- package/src/domain/usecases/AnalyzeStoriesUseCase.ts +2 -2
- package/src/domain/usecases/ChangeStatusByStoryColorUseCase.test.ts +0 -5
- package/src/domain/usecases/ChangeStatusByStoryColorUseCase.ts +5 -5
- package/src/domain/usecases/ConvertCheckboxToIssueInStoryIssueUseCase.test.ts +2 -13
- package/src/domain/usecases/ConvertCheckboxToIssueInStoryIssueUseCase.ts +2 -2
- package/src/domain/usecases/CreateEstimationIssueUseCase.ts +2 -2
- package/src/domain/usecases/CreateNewStoryByLabelUseCase.test.ts +0 -4
- package/src/domain/usecases/CreateNewStoryByLabelUseCase.ts +0 -1
- package/src/domain/usecases/HandleScheduledEventUseCase.test.ts +4 -41
- package/src/domain/usecases/HandleScheduledEventUseCase.ts +9 -27
- package/src/domain/usecases/NotifyFinishedIssuePreparationUseCase.test.ts +0 -202
- package/src/domain/usecases/NotifyFinishedIssuePreparationUseCase.ts +18 -31
- package/src/domain/usecases/RevertOrphanedPreparationUseCase.test.ts +13 -101
- package/src/domain/usecases/RevertOrphanedPreparationUseCase.ts +10 -10
- package/src/domain/usecases/SetupTowerDefenceProjectUseCase.test.ts +266 -0
- package/src/domain/usecases/SetupTowerDefenceProjectUseCase.ts +73 -0
- package/src/domain/usecases/StartPreparationUseCase.test.ts +1 -151
- package/src/domain/usecases/StartPreparationUseCase.ts +11 -20
- package/src/domain/usecases/UpdateIssueStatusByLabelUseCase.test.ts +2 -47
- package/src/domain/usecases/UpdateIssueStatusByLabelUseCase.ts +1 -5
- package/src/domain/usecases/adapter-interfaces/ProjectRepository.ts +6 -1
- package/types/adapter/entry-points/cli/index.d.ts.map +1 -1
- package/types/adapter/entry-points/cli/projectConfig.d.ts +0 -3
- package/types/adapter/entry-points/cli/projectConfig.d.ts.map +1 -1
- package/types/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.d.ts.map +1 -1
- package/types/adapter/repositories/GraphqlProjectRepository.d.ts +4 -1
- package/types/adapter/repositories/GraphqlProjectRepository.d.ts.map +1 -1
- package/types/domain/entities/WorkflowStatus.d.ts +17 -0
- package/types/domain/entities/WorkflowStatus.d.ts.map +1 -0
- package/types/domain/usecases/AnalyzeStoriesUseCase.d.ts +0 -1
- package/types/domain/usecases/AnalyzeStoriesUseCase.d.ts.map +1 -1
- package/types/domain/usecases/ChangeStatusByStoryColorUseCase.d.ts +0 -1
- package/types/domain/usecases/ChangeStatusByStoryColorUseCase.d.ts.map +1 -1
- package/types/domain/usecases/ConvertCheckboxToIssueInStoryIssueUseCase.d.ts +0 -1
- package/types/domain/usecases/ConvertCheckboxToIssueInStoryIssueUseCase.d.ts.map +1 -1
- package/types/domain/usecases/CreateEstimationIssueUseCase.d.ts +0 -1
- package/types/domain/usecases/CreateEstimationIssueUseCase.d.ts.map +1 -1
- package/types/domain/usecases/CreateNewStoryByLabelUseCase.d.ts +0 -1
- package/types/domain/usecases/CreateNewStoryByLabelUseCase.d.ts.map +1 -1
- package/types/domain/usecases/HandleScheduledEventUseCase.d.ts +3 -8
- package/types/domain/usecases/HandleScheduledEventUseCase.d.ts.map +1 -1
- package/types/domain/usecases/NotifyFinishedIssuePreparationUseCase.d.ts +1 -4
- package/types/domain/usecases/NotifyFinishedIssuePreparationUseCase.d.ts.map +1 -1
- package/types/domain/usecases/RevertOrphanedPreparationUseCase.d.ts +0 -3
- package/types/domain/usecases/RevertOrphanedPreparationUseCase.d.ts.map +1 -1
- package/types/domain/usecases/SetupTowerDefenceProjectUseCase.d.ts +10 -0
- package/types/domain/usecases/SetupTowerDefenceProjectUseCase.d.ts.map +1 -0
- package/types/domain/usecases/StartPreparationUseCase.d.ts +1 -3
- package/types/domain/usecases/StartPreparationUseCase.d.ts.map +1 -1
- package/types/domain/usecases/UpdateIssueStatusByLabelUseCase.d.ts +0 -1
- package/types/domain/usecases/UpdateIssueStatusByLabelUseCase.d.ts.map +1 -1
- package/types/domain/usecases/adapter-interfaces/ProjectRepository.d.ts +3 -1
- package/types/domain/usecases/adapter-interfaces/ProjectRepository.d.ts.map +1 -1
|
@@ -158,9 +158,6 @@ describe('RevertOrphanedPreparationUseCase', () => {
|
|
|
158
158
|
|
|
159
159
|
await useCase.run({
|
|
160
160
|
projectUrl: 'https://github.com/user/repo',
|
|
161
|
-
preparationStatus: 'Preparation',
|
|
162
|
-
awaitingWorkspaceStatus: 'Awaiting Workspace',
|
|
163
|
-
awaitingQualityCheckStatus: 'Awaiting Quality Check',
|
|
164
161
|
allowIssueCacheMinutes: 60,
|
|
165
162
|
preparationProcessCheckCommand: 'pgrep -fa "claude-agent.*{URL}"',
|
|
166
163
|
});
|
|
@@ -206,9 +203,6 @@ describe('RevertOrphanedPreparationUseCase', () => {
|
|
|
206
203
|
|
|
207
204
|
await useCase.run({
|
|
208
205
|
projectUrl: 'https://github.com/user/repo',
|
|
209
|
-
preparationStatus: 'Preparation',
|
|
210
|
-
awaitingWorkspaceStatus: 'Awaiting Workspace',
|
|
211
|
-
awaitingQualityCheckStatus: 'Awaiting Quality Check',
|
|
212
206
|
allowIssueCacheMinutes: 60,
|
|
213
207
|
preparationProcessCheckCommand: 'pgrep -fa "claude-agent.*{URL}"',
|
|
214
208
|
});
|
|
@@ -247,9 +241,6 @@ describe('RevertOrphanedPreparationUseCase', () => {
|
|
|
247
241
|
|
|
248
242
|
await useCase.run({
|
|
249
243
|
projectUrl: 'https://github.com/user/repo',
|
|
250
|
-
preparationStatus: 'Preparation',
|
|
251
|
-
awaitingWorkspaceStatus: 'Awaiting Workspace',
|
|
252
|
-
awaitingQualityCheckStatus: 'Awaiting Quality Check',
|
|
253
244
|
allowIssueCacheMinutes: 60,
|
|
254
245
|
preparationProcessCheckCommand: 'pgrep -fa "claude-agent.*{URL}"',
|
|
255
246
|
});
|
|
@@ -283,46 +274,6 @@ describe('RevertOrphanedPreparationUseCase', () => {
|
|
|
283
274
|
|
|
284
275
|
await useCase.run({
|
|
285
276
|
projectUrl: 'https://github.com/user/repo',
|
|
286
|
-
preparationStatus: 'Preparation',
|
|
287
|
-
awaitingWorkspaceStatus: 'Awaiting Workspace',
|
|
288
|
-
awaitingQualityCheckStatus: 'Awaiting Quality Check',
|
|
289
|
-
allowIssueCacheMinutes: 60,
|
|
290
|
-
preparationProcessCheckCommand: 'pgrep -fa "claude-agent.*{URL}"',
|
|
291
|
-
});
|
|
292
|
-
|
|
293
|
-
expect(mockIssueRepository.updateStatus.mock.calls).toHaveLength(1);
|
|
294
|
-
expect(mockIssueRepository.updateStatus.mock.calls[0][2]).toBe('1');
|
|
295
|
-
});
|
|
296
|
-
|
|
297
|
-
it('should revert orphaned issue to Awaiting Workspace when awaitingQualityCheckStatus is not provided', async () => {
|
|
298
|
-
const stuckIssue = createMockIssue({
|
|
299
|
-
url: 'https://github.com/user/repo/issues/10',
|
|
300
|
-
status: 'Preparation',
|
|
301
|
-
});
|
|
302
|
-
mockIssueRepository.getAllIssues.mockResolvedValue({
|
|
303
|
-
issues: [stuckIssue],
|
|
304
|
-
cacheUsed: false,
|
|
305
|
-
});
|
|
306
|
-
mockLocalCommandRunner.runCommand.mockResolvedValue({
|
|
307
|
-
stdout: '',
|
|
308
|
-
stderr: '',
|
|
309
|
-
exitCode: 1,
|
|
310
|
-
});
|
|
311
|
-
mockIssueCommentRepository.getCommentsFromIssue.mockResolvedValue([
|
|
312
|
-
{
|
|
313
|
-
author: 'bot',
|
|
314
|
-
content: 'From: agent report',
|
|
315
|
-
createdAt: new Date(),
|
|
316
|
-
},
|
|
317
|
-
]);
|
|
318
|
-
mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([
|
|
319
|
-
createPassingPr(),
|
|
320
|
-
]);
|
|
321
|
-
|
|
322
|
-
await useCase.run({
|
|
323
|
-
projectUrl: 'https://github.com/user/repo',
|
|
324
|
-
preparationStatus: 'Preparation',
|
|
325
|
-
awaitingWorkspaceStatus: 'Awaiting Workspace',
|
|
326
277
|
allowIssueCacheMinutes: 60,
|
|
327
278
|
preparationProcessCheckCommand: 'pgrep -fa "claude-agent.*{URL}"',
|
|
328
279
|
});
|
|
@@ -356,9 +307,6 @@ describe('RevertOrphanedPreparationUseCase', () => {
|
|
|
356
307
|
|
|
357
308
|
await useCase.run({
|
|
358
309
|
projectUrl: 'https://github.com/user/repo',
|
|
359
|
-
preparationStatus: 'Preparation',
|
|
360
|
-
awaitingWorkspaceStatus: 'Awaiting Workspace',
|
|
361
|
-
awaitingQualityCheckStatus: 'Awaiting Quality Check',
|
|
362
310
|
allowIssueCacheMinutes: 60,
|
|
363
311
|
preparationProcessCheckCommand: 'pgrep -fa "claude-agent.*{URL}"',
|
|
364
312
|
});
|
|
@@ -393,9 +341,6 @@ describe('RevertOrphanedPreparationUseCase', () => {
|
|
|
393
341
|
|
|
394
342
|
await useCase.run({
|
|
395
343
|
projectUrl: 'https://github.com/user/repo',
|
|
396
|
-
preparationStatus: 'Preparation',
|
|
397
|
-
awaitingWorkspaceStatus: 'Awaiting Workspace',
|
|
398
|
-
awaitingQualityCheckStatus: 'Awaiting Quality Check',
|
|
399
344
|
allowIssueCacheMinutes: 60,
|
|
400
345
|
preparationProcessCheckCommand: 'pgrep -fa "claude-agent.*{URL}"',
|
|
401
346
|
});
|
|
@@ -422,9 +367,6 @@ describe('RevertOrphanedPreparationUseCase', () => {
|
|
|
422
367
|
|
|
423
368
|
await useCase.run({
|
|
424
369
|
projectUrl: 'https://github.com/user/repo',
|
|
425
|
-
preparationStatus: 'Preparation',
|
|
426
|
-
awaitingWorkspaceStatus: 'Awaiting Workspace',
|
|
427
|
-
awaitingQualityCheckStatus: 'Awaiting Quality Check',
|
|
428
370
|
allowIssueCacheMinutes: 60,
|
|
429
371
|
preparationProcessCheckCommand: 'pgrep -fa "claude-agent.*{URL}"',
|
|
430
372
|
});
|
|
@@ -449,9 +391,6 @@ describe('RevertOrphanedPreparationUseCase', () => {
|
|
|
449
391
|
|
|
450
392
|
await useCase.run({
|
|
451
393
|
projectUrl: 'https://github.com/user/repo',
|
|
452
|
-
preparationStatus: 'Preparation',
|
|
453
|
-
awaitingWorkspaceStatus: 'Awaiting Workspace',
|
|
454
|
-
awaitingQualityCheckStatus: 'Awaiting Quality Check',
|
|
455
394
|
allowIssueCacheMinutes: 60,
|
|
456
395
|
preparationProcessCheckCommand: 'pgrep -fa "claude-agent.*{URL}"',
|
|
457
396
|
});
|
|
@@ -484,9 +423,6 @@ describe('RevertOrphanedPreparationUseCase', () => {
|
|
|
484
423
|
|
|
485
424
|
await useCase.run({
|
|
486
425
|
projectUrl: 'https://github.com/user/repo',
|
|
487
|
-
preparationStatus: 'Preparation',
|
|
488
|
-
awaitingWorkspaceStatus: 'Awaiting Workspace',
|
|
489
|
-
awaitingQualityCheckStatus: 'Awaiting Quality Check',
|
|
490
426
|
allowIssueCacheMinutes: 60,
|
|
491
427
|
preparationProcessCheckCommand: 'check {URL}',
|
|
492
428
|
});
|
|
@@ -526,9 +462,6 @@ describe('RevertOrphanedPreparationUseCase', () => {
|
|
|
526
462
|
|
|
527
463
|
await useCase.run({
|
|
528
464
|
projectUrl: 'https://github.com/user/repo',
|
|
529
|
-
preparationStatus: 'Preparation',
|
|
530
|
-
awaitingWorkspaceStatus: 'Awaiting Workspace',
|
|
531
|
-
awaitingQualityCheckStatus: 'Awaiting Quality Check',
|
|
532
465
|
allowIssueCacheMinutes: 60,
|
|
533
466
|
preparationProcessCheckCommand: 'check {URL}',
|
|
534
467
|
});
|
|
@@ -543,9 +476,6 @@ describe('RevertOrphanedPreparationUseCase', () => {
|
|
|
543
476
|
await expect(
|
|
544
477
|
useCase.run({
|
|
545
478
|
projectUrl: 'https://github.com/user/repo',
|
|
546
|
-
preparationStatus: 'Preparation',
|
|
547
|
-
awaitingWorkspaceStatus: 'Awaiting Workspace',
|
|
548
|
-
awaitingQualityCheckStatus: 'Awaiting Quality Check',
|
|
549
479
|
allowIssueCacheMinutes: 0,
|
|
550
480
|
preparationProcessCheckCommand: 'check {URL}',
|
|
551
481
|
}),
|
|
@@ -559,20 +489,29 @@ describe('RevertOrphanedPreparationUseCase', () => {
|
|
|
559
489
|
await expect(
|
|
560
490
|
useCase.run({
|
|
561
491
|
projectUrl: 'https://github.com/user/repo',
|
|
562
|
-
preparationStatus: 'Preparation',
|
|
563
|
-
awaitingWorkspaceStatus: 'Awaiting Workspace',
|
|
564
|
-
awaitingQualityCheckStatus: 'Awaiting Quality Check',
|
|
565
492
|
allowIssueCacheMinutes: 0,
|
|
566
493
|
preparationProcessCheckCommand: 'check {URL}',
|
|
567
494
|
}),
|
|
568
495
|
).rejects.toThrow('Project not found. projectId: project-1');
|
|
569
496
|
});
|
|
570
497
|
|
|
571
|
-
it('should do nothing when
|
|
498
|
+
it('should do nothing when Awaiting Workspace status is not found in project statuses', async () => {
|
|
572
499
|
const preparationIssue = createMockIssue({
|
|
573
500
|
url: 'https://github.com/user/repo/issues/10',
|
|
574
501
|
status: 'Preparation',
|
|
575
502
|
});
|
|
503
|
+
const projectWithoutAwaitingWorkspace = {
|
|
504
|
+
...mockProject,
|
|
505
|
+
status: {
|
|
506
|
+
...mockProject.status,
|
|
507
|
+
statuses: mockProject.status.statuses.filter(
|
|
508
|
+
(s) => s.name !== 'Awaiting Workspace',
|
|
509
|
+
),
|
|
510
|
+
},
|
|
511
|
+
};
|
|
512
|
+
mockProjectRepository.getProject.mockResolvedValue(
|
|
513
|
+
projectWithoutAwaitingWorkspace,
|
|
514
|
+
);
|
|
576
515
|
mockIssueRepository.getAllIssues.mockResolvedValue({
|
|
577
516
|
issues: [preparationIssue],
|
|
578
517
|
cacheUsed: false,
|
|
@@ -585,9 +524,6 @@ describe('RevertOrphanedPreparationUseCase', () => {
|
|
|
585
524
|
|
|
586
525
|
await useCase.run({
|
|
587
526
|
projectUrl: 'https://github.com/user/repo',
|
|
588
|
-
preparationStatus: 'Preparation',
|
|
589
|
-
awaitingWorkspaceStatus: 'NonExistentStatus',
|
|
590
|
-
awaitingQualityCheckStatus: 'Awaiting Quality Check',
|
|
591
527
|
allowIssueCacheMinutes: 0,
|
|
592
528
|
preparationProcessCheckCommand: 'check {URL}',
|
|
593
529
|
});
|
|
@@ -606,9 +542,6 @@ describe('RevertOrphanedPreparationUseCase', () => {
|
|
|
606
542
|
|
|
607
543
|
await useCase.run({
|
|
608
544
|
projectUrl: 'https://github.com/user/repo',
|
|
609
|
-
preparationStatus: 'Preparation',
|
|
610
|
-
awaitingWorkspaceStatus: 'Awaiting Workspace',
|
|
611
|
-
awaitingQualityCheckStatus: 'Awaiting Quality Check',
|
|
612
545
|
allowIssueCacheMinutes: 60,
|
|
613
546
|
preparationProcessCheckCommand: 'check {URL}',
|
|
614
547
|
});
|
|
@@ -645,9 +578,6 @@ describe('RevertOrphanedPreparationUseCase', () => {
|
|
|
645
578
|
|
|
646
579
|
await useCase.run({
|
|
647
580
|
projectUrl: 'https://github.com/user/repo',
|
|
648
|
-
preparationStatus: 'Preparation',
|
|
649
|
-
awaitingWorkspaceStatus: 'Awaiting Workspace',
|
|
650
|
-
awaitingQualityCheckStatus: 'Awaiting Quality Check',
|
|
651
581
|
allowIssueCacheMinutes: 60,
|
|
652
582
|
preparationProcessCheckCommand: 'pgrep -fa "Please handover {URL}"',
|
|
653
583
|
awLogDirectoryPath: '/home/user/logs-aw',
|
|
@@ -710,9 +640,6 @@ describe('RevertOrphanedPreparationUseCase', () => {
|
|
|
710
640
|
|
|
711
641
|
await useCase.run({
|
|
712
642
|
projectUrl: 'https://github.com/user/repo',
|
|
713
|
-
preparationStatus: 'Preparation',
|
|
714
|
-
awaitingWorkspaceStatus: 'Awaiting Workspace',
|
|
715
|
-
awaitingQualityCheckStatus: 'Awaiting Quality Check',
|
|
716
643
|
allowIssueCacheMinutes: 60,
|
|
717
644
|
preparationProcessCheckCommand: 'pgrep -fa "Please handover {URL}"',
|
|
718
645
|
awLogDirectoryPath: '/home/user/logs-aw',
|
|
@@ -747,9 +674,6 @@ describe('RevertOrphanedPreparationUseCase', () => {
|
|
|
747
674
|
|
|
748
675
|
await useCase.run({
|
|
749
676
|
projectUrl: 'https://github.com/user/repo',
|
|
750
|
-
preparationStatus: 'Preparation',
|
|
751
|
-
awaitingWorkspaceStatus: 'Awaiting Workspace',
|
|
752
|
-
awaitingQualityCheckStatus: 'Awaiting Quality Check',
|
|
753
677
|
allowIssueCacheMinutes: 60,
|
|
754
678
|
preparationProcessCheckCommand: 'pgrep -fa "Please handover {URL}"',
|
|
755
679
|
awLogDirectoryPath: '/home/user/logs-aw',
|
|
@@ -780,9 +704,6 @@ describe('RevertOrphanedPreparationUseCase', () => {
|
|
|
780
704
|
|
|
781
705
|
await useCase.run({
|
|
782
706
|
projectUrl: 'https://github.com/user/repo',
|
|
783
|
-
preparationStatus: 'Preparation',
|
|
784
|
-
awaitingWorkspaceStatus: 'Awaiting Workspace',
|
|
785
|
-
awaitingQualityCheckStatus: 'Awaiting Quality Check',
|
|
786
707
|
allowIssueCacheMinutes: 60,
|
|
787
708
|
preparationProcessCheckCommand: 'pgrep -fa "claude-agent.*{URL}"',
|
|
788
709
|
});
|
|
@@ -812,9 +733,6 @@ describe('RevertOrphanedPreparationUseCase', () => {
|
|
|
812
733
|
|
|
813
734
|
await useCase.run({
|
|
814
735
|
projectUrl: 'https://github.com/user/repo',
|
|
815
|
-
preparationStatus: 'Preparation',
|
|
816
|
-
awaitingWorkspaceStatus: 'Awaiting Workspace',
|
|
817
|
-
awaitingQualityCheckStatus: 'Awaiting Quality Check',
|
|
818
736
|
allowIssueCacheMinutes: 60,
|
|
819
737
|
preparationProcessCheckCommand: 'pgrep -fa "Please handover {URL}"',
|
|
820
738
|
awLogDirectoryPath: '/home/user/logs-aw',
|
|
@@ -842,9 +760,6 @@ describe('RevertOrphanedPreparationUseCase', () => {
|
|
|
842
760
|
|
|
843
761
|
await useCase.run({
|
|
844
762
|
projectUrl: 'https://github.com/user/repo',
|
|
845
|
-
preparationStatus: 'Preparation',
|
|
846
|
-
awaitingWorkspaceStatus: 'Awaiting Workspace',
|
|
847
|
-
awaitingQualityCheckStatus: 'Awaiting Quality Check',
|
|
848
763
|
allowIssueCacheMinutes: 0,
|
|
849
764
|
preparationProcessCheckCommand: 'pgrep -fa "claude-agent.*{URL}"',
|
|
850
765
|
});
|
|
@@ -877,9 +792,6 @@ describe('RevertOrphanedPreparationUseCase', () => {
|
|
|
877
792
|
|
|
878
793
|
await useCase.run({
|
|
879
794
|
projectUrl: 'https://github.com/user/repo',
|
|
880
|
-
preparationStatus: 'Preparation',
|
|
881
|
-
awaitingWorkspaceStatus: 'Awaiting Workspace',
|
|
882
|
-
awaitingQualityCheckStatus: 'Awaiting Quality Check',
|
|
883
795
|
allowIssueCacheMinutes: 60,
|
|
884
796
|
preparationProcessCheckCommand: 'pgrep -fa "claude-agent.*{URL}"',
|
|
885
797
|
});
|
|
@@ -6,6 +6,11 @@ import { IssueCommentRepository } from './adapter-interfaces/IssueCommentReposit
|
|
|
6
6
|
import { ProjectRepository } from './adapter-interfaces/ProjectRepository';
|
|
7
7
|
import { LocalCommandRunner } from './adapter-interfaces/LocalCommandRunner';
|
|
8
8
|
import { Issue } from '../entities/Issue';
|
|
9
|
+
import {
|
|
10
|
+
AWAITING_QUALITY_CHECK_STATUS_NAME,
|
|
11
|
+
AWAITING_WORKSPACE_STATUS_NAME,
|
|
12
|
+
PREPARATION_STATUS_NAME,
|
|
13
|
+
} from '../entities/WorkflowStatus';
|
|
9
14
|
|
|
10
15
|
export class RevertOrphanedPreparationUseCase {
|
|
11
16
|
constructor(
|
|
@@ -29,9 +34,6 @@ export class RevertOrphanedPreparationUseCase {
|
|
|
29
34
|
|
|
30
35
|
run = async (params: {
|
|
31
36
|
projectUrl: string;
|
|
32
|
-
preparationStatus: string;
|
|
33
|
-
awaitingWorkspaceStatus: string;
|
|
34
|
-
awaitingQualityCheckStatus?: string;
|
|
35
37
|
allowIssueCacheMinutes: number;
|
|
36
38
|
preparationProcessCheckCommand: string;
|
|
37
39
|
awLogDirectoryPath?: string;
|
|
@@ -55,21 +57,19 @@ export class RevertOrphanedPreparationUseCase {
|
|
|
55
57
|
);
|
|
56
58
|
|
|
57
59
|
const preparationIssues = issues.filter(
|
|
58
|
-
(issue) => issue.status ===
|
|
60
|
+
(issue) => issue.status === PREPARATION_STATUS_NAME,
|
|
59
61
|
);
|
|
60
62
|
|
|
61
63
|
const awaitingWorkspaceStatusOption = project.status.statuses.find(
|
|
62
|
-
(s) => s.name ===
|
|
64
|
+
(s) => s.name === AWAITING_WORKSPACE_STATUS_NAME,
|
|
63
65
|
);
|
|
64
66
|
if (!awaitingWorkspaceStatusOption) {
|
|
65
67
|
return;
|
|
66
68
|
}
|
|
67
69
|
|
|
68
|
-
const awaitingQualityCheckStatusOption =
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
)
|
|
72
|
-
: null;
|
|
70
|
+
const awaitingQualityCheckStatusOption = project.status.statuses.find(
|
|
71
|
+
(s) => s.name === AWAITING_QUALITY_CHECK_STATUS_NAME,
|
|
72
|
+
);
|
|
73
73
|
|
|
74
74
|
for (const issue of preparationIssues) {
|
|
75
75
|
const isOrphaned = await this.isOrphanedIssue(issue, params);
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
import { mock } from 'jest-mock-extended';
|
|
2
|
+
import { SetupTowerDefenceProjectUseCase } from './SetupTowerDefenceProjectUseCase';
|
|
3
|
+
import { ProjectRepository } from './adapter-interfaces/ProjectRepository';
|
|
4
|
+
import { FieldOption, Project } from '../entities/Project';
|
|
5
|
+
import {
|
|
6
|
+
AWAITING_QUALITY_CHECK_STATUS_NAME,
|
|
7
|
+
AWAITING_TASK_BREAKDOWN_STATUS_NAME,
|
|
8
|
+
AWAITING_WORKSPACE_STATUS_NAME,
|
|
9
|
+
DEFAULT_STATUS_NAME,
|
|
10
|
+
DONE_STATUS_NAME,
|
|
11
|
+
ICEBOX_STATUS_NAME,
|
|
12
|
+
IN_TMUX_STATUS_NAME,
|
|
13
|
+
PC_TODO_STATUS_NAME,
|
|
14
|
+
PREPARATION_STATUS_NAME,
|
|
15
|
+
REQUIRED_WORKFLOW_STATUSES,
|
|
16
|
+
TODO_STATUS_NAME,
|
|
17
|
+
} from '../entities/WorkflowStatus';
|
|
18
|
+
|
|
19
|
+
const buildProject = (statuses: FieldOption[]): Project => ({
|
|
20
|
+
id: 'project-1',
|
|
21
|
+
url: 'https://github.com/orgs/test-org/projects/1',
|
|
22
|
+
databaseId: 1,
|
|
23
|
+
name: 'test-project',
|
|
24
|
+
status: {
|
|
25
|
+
name: 'Status',
|
|
26
|
+
fieldId: 'status-field-1',
|
|
27
|
+
statuses,
|
|
28
|
+
},
|
|
29
|
+
nextActionDate: null,
|
|
30
|
+
nextActionHour: null,
|
|
31
|
+
story: null,
|
|
32
|
+
remainingEstimationMinutes: null,
|
|
33
|
+
dependedIssueUrlSeparatedByComma: null,
|
|
34
|
+
completionDate50PercentConfidence: null,
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
const buildCanonicalStatuses = (): FieldOption[] =>
|
|
38
|
+
REQUIRED_WORKFLOW_STATUSES.map((required, index) => ({
|
|
39
|
+
id: `id-${index}`,
|
|
40
|
+
name: required.name,
|
|
41
|
+
color: required.color,
|
|
42
|
+
description: '',
|
|
43
|
+
}));
|
|
44
|
+
|
|
45
|
+
describe('SetupTowerDefenceProjectUseCase', () => {
|
|
46
|
+
it('should define exactly the 10 required statuses in the documented order with the documented colors and no descriptions', () => {
|
|
47
|
+
expect(REQUIRED_WORKFLOW_STATUSES).toEqual([
|
|
48
|
+
{ name: DEFAULT_STATUS_NAME, color: 'ORANGE' },
|
|
49
|
+
{ name: AWAITING_TASK_BREAKDOWN_STATUS_NAME, color: 'ORANGE' },
|
|
50
|
+
{ name: AWAITING_WORKSPACE_STATUS_NAME, color: 'BLUE' },
|
|
51
|
+
{ name: PREPARATION_STATUS_NAME, color: 'YELLOW' },
|
|
52
|
+
{ name: AWAITING_QUALITY_CHECK_STATUS_NAME, color: 'GREEN' },
|
|
53
|
+
{ name: TODO_STATUS_NAME, color: 'PINK' },
|
|
54
|
+
{ name: PC_TODO_STATUS_NAME, color: 'PINK' },
|
|
55
|
+
{ name: IN_TMUX_STATUS_NAME, color: 'RED' },
|
|
56
|
+
{ name: DONE_STATUS_NAME, color: 'PURPLE' },
|
|
57
|
+
{ name: ICEBOX_STATUS_NAME, color: 'GRAY' },
|
|
58
|
+
]);
|
|
59
|
+
for (const required of REQUIRED_WORKFLOW_STATUSES) {
|
|
60
|
+
expect(required).not.toHaveProperty('description');
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('should skip update when project already has required statuses in canonical order with no descriptions', async () => {
|
|
65
|
+
const mockProjectRepository =
|
|
66
|
+
mock<Pick<ProjectRepository, 'getByUrl' | 'updateStatusList'>>();
|
|
67
|
+
const project = buildProject(buildCanonicalStatuses());
|
|
68
|
+
mockProjectRepository.getByUrl.mockResolvedValue(project);
|
|
69
|
+
|
|
70
|
+
const useCase = new SetupTowerDefenceProjectUseCase(mockProjectRepository);
|
|
71
|
+
await useCase.run({ projectUrl: project.url });
|
|
72
|
+
|
|
73
|
+
expect(mockProjectRepository.updateStatusList).not.toHaveBeenCalled();
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('should skip update when project already has required statuses plus extras after them', async () => {
|
|
77
|
+
const mockProjectRepository =
|
|
78
|
+
mock<Pick<ProjectRepository, 'getByUrl' | 'updateStatusList'>>();
|
|
79
|
+
const statuses = [
|
|
80
|
+
...buildCanonicalStatuses(),
|
|
81
|
+
{
|
|
82
|
+
id: 'extra-1',
|
|
83
|
+
name: 'Custom Extra Status',
|
|
84
|
+
color: 'GREEN' as const,
|
|
85
|
+
description: '',
|
|
86
|
+
},
|
|
87
|
+
];
|
|
88
|
+
const project = buildProject(statuses);
|
|
89
|
+
mockProjectRepository.getByUrl.mockResolvedValue(project);
|
|
90
|
+
|
|
91
|
+
const useCase = new SetupTowerDefenceProjectUseCase(mockProjectRepository);
|
|
92
|
+
await useCase.run({ projectUrl: project.url });
|
|
93
|
+
|
|
94
|
+
expect(mockProjectRepository.updateStatusList).not.toHaveBeenCalled();
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('should rewrite required statuses with empty descriptions when an existing status carries a description', async () => {
|
|
98
|
+
const mockProjectRepository =
|
|
99
|
+
mock<Pick<ProjectRepository, 'getByUrl' | 'updateStatusList'>>();
|
|
100
|
+
const statuses = buildCanonicalStatuses();
|
|
101
|
+
statuses[0] = { ...statuses[0], description: 'stale description' };
|
|
102
|
+
const project = buildProject(statuses);
|
|
103
|
+
mockProjectRepository.getByUrl.mockResolvedValue(project);
|
|
104
|
+
mockProjectRepository.updateStatusList.mockResolvedValue([]);
|
|
105
|
+
|
|
106
|
+
const useCase = new SetupTowerDefenceProjectUseCase(mockProjectRepository);
|
|
107
|
+
await useCase.run({ projectUrl: project.url });
|
|
108
|
+
|
|
109
|
+
expect(mockProjectRepository.updateStatusList).toHaveBeenCalledTimes(1);
|
|
110
|
+
const [, payload] = mockProjectRepository.updateStatusList.mock.calls[0];
|
|
111
|
+
expect(
|
|
112
|
+
payload
|
|
113
|
+
.slice(0, REQUIRED_WORKFLOW_STATUSES.length)
|
|
114
|
+
.every((status) => status.description === ''),
|
|
115
|
+
).toBe(true);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it('should add missing required statuses while preserving existing custom statuses', async () => {
|
|
119
|
+
const mockProjectRepository =
|
|
120
|
+
mock<Pick<ProjectRepository, 'getByUrl' | 'updateStatusList'>>();
|
|
121
|
+
const statuses: FieldOption[] = [
|
|
122
|
+
{
|
|
123
|
+
id: 'unread-id',
|
|
124
|
+
name: DEFAULT_STATUS_NAME,
|
|
125
|
+
color: 'ORANGE',
|
|
126
|
+
description: '',
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
id: 'extra-1',
|
|
130
|
+
name: 'Custom Extra Status',
|
|
131
|
+
color: 'GREEN',
|
|
132
|
+
description: '',
|
|
133
|
+
},
|
|
134
|
+
];
|
|
135
|
+
const project = buildProject(statuses);
|
|
136
|
+
mockProjectRepository.getByUrl.mockResolvedValue(project);
|
|
137
|
+
mockProjectRepository.updateStatusList.mockResolvedValue([]);
|
|
138
|
+
|
|
139
|
+
const useCase = new SetupTowerDefenceProjectUseCase(mockProjectRepository);
|
|
140
|
+
await useCase.run({ projectUrl: project.url });
|
|
141
|
+
|
|
142
|
+
expect(mockProjectRepository.updateStatusList).toHaveBeenCalledTimes(1);
|
|
143
|
+
expect(mockProjectRepository.updateStatusList).toHaveBeenCalledWith(
|
|
144
|
+
project,
|
|
145
|
+
[
|
|
146
|
+
{
|
|
147
|
+
id: 'unread-id',
|
|
148
|
+
name: DEFAULT_STATUS_NAME,
|
|
149
|
+
color: 'ORANGE',
|
|
150
|
+
description: '',
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
id: null,
|
|
154
|
+
name: AWAITING_TASK_BREAKDOWN_STATUS_NAME,
|
|
155
|
+
color: 'ORANGE',
|
|
156
|
+
description: '',
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
id: null,
|
|
160
|
+
name: AWAITING_WORKSPACE_STATUS_NAME,
|
|
161
|
+
color: 'BLUE',
|
|
162
|
+
description: '',
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
id: null,
|
|
166
|
+
name: PREPARATION_STATUS_NAME,
|
|
167
|
+
color: 'YELLOW',
|
|
168
|
+
description: '',
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
id: null,
|
|
172
|
+
name: AWAITING_QUALITY_CHECK_STATUS_NAME,
|
|
173
|
+
color: 'GREEN',
|
|
174
|
+
description: '',
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
id: null,
|
|
178
|
+
name: TODO_STATUS_NAME,
|
|
179
|
+
color: 'PINK',
|
|
180
|
+
description: '',
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
id: null,
|
|
184
|
+
name: PC_TODO_STATUS_NAME,
|
|
185
|
+
color: 'PINK',
|
|
186
|
+
description: '',
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
id: null,
|
|
190
|
+
name: IN_TMUX_STATUS_NAME,
|
|
191
|
+
color: 'RED',
|
|
192
|
+
description: '',
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
id: null,
|
|
196
|
+
name: DONE_STATUS_NAME,
|
|
197
|
+
color: 'PURPLE',
|
|
198
|
+
description: '',
|
|
199
|
+
},
|
|
200
|
+
{
|
|
201
|
+
id: null,
|
|
202
|
+
name: ICEBOX_STATUS_NAME,
|
|
203
|
+
color: 'GRAY',
|
|
204
|
+
description: '',
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
id: 'extra-1',
|
|
208
|
+
name: 'Custom Extra Status',
|
|
209
|
+
color: 'GREEN',
|
|
210
|
+
description: '',
|
|
211
|
+
},
|
|
212
|
+
],
|
|
213
|
+
);
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
it('should reorder existing required statuses into canonical order when out of order', async () => {
|
|
217
|
+
const mockProjectRepository =
|
|
218
|
+
mock<Pick<ProjectRepository, 'getByUrl' | 'updateStatusList'>>();
|
|
219
|
+
const reversedStatuses: FieldOption[] = REQUIRED_WORKFLOW_STATUSES.slice()
|
|
220
|
+
.reverse()
|
|
221
|
+
.map((required, index) => ({
|
|
222
|
+
id: `reversed-id-${index}`,
|
|
223
|
+
name: required.name,
|
|
224
|
+
color: required.color,
|
|
225
|
+
description: '',
|
|
226
|
+
}));
|
|
227
|
+
const project = buildProject(reversedStatuses);
|
|
228
|
+
mockProjectRepository.getByUrl.mockResolvedValue(project);
|
|
229
|
+
mockProjectRepository.updateStatusList.mockResolvedValue([]);
|
|
230
|
+
|
|
231
|
+
const useCase = new SetupTowerDefenceProjectUseCase(mockProjectRepository);
|
|
232
|
+
await useCase.run({ projectUrl: project.url });
|
|
233
|
+
|
|
234
|
+
expect(mockProjectRepository.updateStatusList).toHaveBeenCalledTimes(1);
|
|
235
|
+
const [, payload] = mockProjectRepository.updateStatusList.mock.calls[0];
|
|
236
|
+
expect(payload.map((status) => status.name)).toEqual([
|
|
237
|
+
DEFAULT_STATUS_NAME,
|
|
238
|
+
AWAITING_TASK_BREAKDOWN_STATUS_NAME,
|
|
239
|
+
AWAITING_WORKSPACE_STATUS_NAME,
|
|
240
|
+
PREPARATION_STATUS_NAME,
|
|
241
|
+
AWAITING_QUALITY_CHECK_STATUS_NAME,
|
|
242
|
+
TODO_STATUS_NAME,
|
|
243
|
+
PC_TODO_STATUS_NAME,
|
|
244
|
+
IN_TMUX_STATUS_NAME,
|
|
245
|
+
DONE_STATUS_NAME,
|
|
246
|
+
ICEBOX_STATUS_NAME,
|
|
247
|
+
]);
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
it('should fix color when an existing required status has wrong color', async () => {
|
|
251
|
+
const mockProjectRepository =
|
|
252
|
+
mock<Pick<ProjectRepository, 'getByUrl' | 'updateStatusList'>>();
|
|
253
|
+
const statuses = buildCanonicalStatuses();
|
|
254
|
+
statuses[2] = { ...statuses[2], color: 'RED' };
|
|
255
|
+
const project = buildProject(statuses);
|
|
256
|
+
mockProjectRepository.getByUrl.mockResolvedValue(project);
|
|
257
|
+
mockProjectRepository.updateStatusList.mockResolvedValue([]);
|
|
258
|
+
|
|
259
|
+
const useCase = new SetupTowerDefenceProjectUseCase(mockProjectRepository);
|
|
260
|
+
await useCase.run({ projectUrl: project.url });
|
|
261
|
+
|
|
262
|
+
expect(mockProjectRepository.updateStatusList).toHaveBeenCalledTimes(1);
|
|
263
|
+
const [, payload] = mockProjectRepository.updateStatusList.mock.calls[0];
|
|
264
|
+
expect(payload[2].color).toBe(REQUIRED_WORKFLOW_STATUSES[2].color);
|
|
265
|
+
});
|
|
266
|
+
});
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { FieldOption } from '../entities/Project';
|
|
2
|
+
import { ProjectRepository } from './adapter-interfaces/ProjectRepository';
|
|
3
|
+
import {
|
|
4
|
+
REQUIRED_WORKFLOW_STATUSES,
|
|
5
|
+
WorkflowStatusDefinition,
|
|
6
|
+
} from '../entities/WorkflowStatus';
|
|
7
|
+
|
|
8
|
+
export class SetupTowerDefenceProjectUseCase {
|
|
9
|
+
constructor(
|
|
10
|
+
private readonly projectRepository: Pick<
|
|
11
|
+
ProjectRepository,
|
|
12
|
+
'getByUrl' | 'updateStatusList'
|
|
13
|
+
>,
|
|
14
|
+
) {}
|
|
15
|
+
|
|
16
|
+
run = async (params: { projectUrl: string }): Promise<void> => {
|
|
17
|
+
const project = await this.projectRepository.getByUrl(params.projectUrl);
|
|
18
|
+
const existing = project.status.statuses;
|
|
19
|
+
|
|
20
|
+
if (
|
|
21
|
+
SetupTowerDefenceProjectUseCase.hasRequiredStatusesInCanonicalOrder(
|
|
22
|
+
existing,
|
|
23
|
+
)
|
|
24
|
+
) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const requiredNames = new Set(
|
|
29
|
+
REQUIRED_WORKFLOW_STATUSES.map((s) => s.name),
|
|
30
|
+
);
|
|
31
|
+
const others = existing.filter((status) => !requiredNames.has(status.name));
|
|
32
|
+
|
|
33
|
+
const newStatusList: (Omit<FieldOption, 'id'> & {
|
|
34
|
+
id: FieldOption['id'] | null;
|
|
35
|
+
})[] = [
|
|
36
|
+
...REQUIRED_WORKFLOW_STATUSES.map((required) => {
|
|
37
|
+
const found = existing.find((status) => status.name === required.name);
|
|
38
|
+
return {
|
|
39
|
+
id: found ? found.id : null,
|
|
40
|
+
name: required.name,
|
|
41
|
+
color: required.color,
|
|
42
|
+
description: '',
|
|
43
|
+
};
|
|
44
|
+
}),
|
|
45
|
+
...others.map((other) => ({
|
|
46
|
+
id: other.id,
|
|
47
|
+
name: other.name,
|
|
48
|
+
color: other.color,
|
|
49
|
+
description: other.description,
|
|
50
|
+
})),
|
|
51
|
+
];
|
|
52
|
+
|
|
53
|
+
await this.projectRepository.updateStatusList(project, newStatusList);
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
private static hasRequiredStatusesInCanonicalOrder = (
|
|
57
|
+
existing: FieldOption[],
|
|
58
|
+
): boolean => {
|
|
59
|
+
if (existing.length < REQUIRED_WORKFLOW_STATUSES.length) {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
return REQUIRED_WORKFLOW_STATUSES.every(
|
|
63
|
+
(required: WorkflowStatusDefinition, index: number) => {
|
|
64
|
+
const actual = existing[index];
|
|
65
|
+
return (
|
|
66
|
+
actual.name === required.name &&
|
|
67
|
+
actual.color === required.color &&
|
|
68
|
+
actual.description === ''
|
|
69
|
+
);
|
|
70
|
+
},
|
|
71
|
+
);
|
|
72
|
+
};
|
|
73
|
+
}
|