npm-cli-gh-issue-preparator 1.22.0 → 1.22.1
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 +7 -0
- package/bin/domain/usecases/StartPreparationUseCase.js +0 -28
- package/bin/domain/usecases/StartPreparationUseCase.js.map +1 -1
- package/package.json +1 -1
- package/src/domain/usecases/StartPreparationUseCase.test.ts +6 -324
- package/src/domain/usecases/StartPreparationUseCase.ts +0 -49
- package/types/domain/usecases/StartPreparationUseCase.d.ts +0 -5
- package/types/domain/usecases/StartPreparationUseCase.d.ts.map +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
## [1.22.1](https://github.com/HiromiShikata/npm-cli-gh-issue-preparator/compare/v1.22.0...v1.22.1) (2026-03-09)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* **core:** remove workflow blocker issue skipping from StartPreparationUseCase ([a6f4b2d](https://github.com/HiromiShikata/npm-cli-gh-issue-preparator/commit/a6f4b2db9406f35628d954f1e1958a4513295b73))
|
|
7
|
+
|
|
1
8
|
# [1.22.0](https://github.com/HiromiShikata/npm-cli-gh-issue-preparator/compare/v1.21.0...v1.22.0) (2026-03-09)
|
|
2
9
|
|
|
3
10
|
|
|
@@ -24,7 +24,6 @@ class StartPreparationUseCase {
|
|
|
24
24
|
project = await this.projectRepository.prepareStatus(params.preparationStatus, project);
|
|
25
25
|
const storyObjectMap = await this.issueRepository.getStoryObjectMap(project);
|
|
26
26
|
const allIssues = await this.issueRepository.getAllOpened(project);
|
|
27
|
-
const repositoryBlockerIssues = this.createWorkflowBockerIsues(storyObjectMap);
|
|
28
27
|
const awaitingWorkspaceIssues = Array.from(storyObjectMap.values())
|
|
29
28
|
.map((storyObject) => storyObject.issues)
|
|
30
29
|
.flat()
|
|
@@ -44,11 +43,6 @@ class StartPreparationUseCase {
|
|
|
44
43
|
for (let i = 0; i < awaitingWorkspaceIssues.length &&
|
|
45
44
|
updatedCurrentPreparationIssueCount < maximumPreparingIssuesCount; i++) {
|
|
46
45
|
const issue = awaitingWorkspaceIssues[i];
|
|
47
|
-
const blockerIssueUrls = repositoryBlockerIssues.find((blocker) => issue.url.includes(blocker.orgRepo))?.blockerIssueUrls || [];
|
|
48
|
-
if (blockerIssueUrls.length > 0 &&
|
|
49
|
-
!blockerIssueUrls.includes(issue.url)) {
|
|
50
|
-
continue;
|
|
51
|
-
}
|
|
52
46
|
if (issue.dependedIssueUrls.length > 0) {
|
|
53
47
|
continue;
|
|
54
48
|
}
|
|
@@ -77,28 +71,6 @@ class StartPreparationUseCase {
|
|
|
77
71
|
updatedCurrentPreparationIssueCount++;
|
|
78
72
|
}
|
|
79
73
|
};
|
|
80
|
-
this.createWorkflowBockerIsues = (storyObjectMap) => {
|
|
81
|
-
const workflowBlockerStory = Array.from(storyObjectMap.keys()).filter((storyName) => storyName.toLowerCase().includes('workflow blocker'));
|
|
82
|
-
if (workflowBlockerStory.length === 0) {
|
|
83
|
-
return [];
|
|
84
|
-
}
|
|
85
|
-
const aggregated = new Map();
|
|
86
|
-
workflowBlockerStory.forEach((storyName) => {
|
|
87
|
-
const issues = storyObjectMap
|
|
88
|
-
.get(storyName)
|
|
89
|
-
?.issues.filter((issue) => issue.state === 'OPEN') || [];
|
|
90
|
-
issues.forEach((issue) => {
|
|
91
|
-
const orgRepo = issue.url.split('/issues')[0].split('github.com/')[1];
|
|
92
|
-
const existing = aggregated.get(orgRepo) || [];
|
|
93
|
-
existing.push(issue.url);
|
|
94
|
-
aggregated.set(orgRepo, existing);
|
|
95
|
-
});
|
|
96
|
-
});
|
|
97
|
-
return Array.from(aggregated.entries()).map(([orgRepo, blockerIssueUrls]) => ({
|
|
98
|
-
orgRepo,
|
|
99
|
-
blockerIssueUrls,
|
|
100
|
-
}));
|
|
101
|
-
};
|
|
102
74
|
}
|
|
103
75
|
}
|
|
104
76
|
exports.StartPreparationUseCase = StartPreparationUseCase;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"StartPreparationUseCase.js","sourceRoot":"","sources":["../../../src/domain/usecases/StartPreparationUseCase.ts"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"StartPreparationUseCase.js","sourceRoot":"","sources":["../../../src/domain/usecases/StartPreparationUseCase.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -440,8 +440,7 @@ describe('StartPreparationUseCase', () => {
|
|
|
440
440
|
expect(mockLocalCommandRunner.runCommand.mock.calls).toHaveLength(6);
|
|
441
441
|
});
|
|
442
442
|
|
|
443
|
-
it('should skip issues from
|
|
444
|
-
// Create a workflow blocker issue
|
|
443
|
+
it('should not skip issues from repositories with workflow blockers', async () => {
|
|
445
444
|
const blockerIssue = createMockIssue({
|
|
446
445
|
url: 'https://github.com/user/repo/issues/100',
|
|
447
446
|
title: 'Blocker Issue',
|
|
@@ -450,16 +449,14 @@ describe('StartPreparationUseCase', () => {
|
|
|
450
449
|
state: 'OPEN',
|
|
451
450
|
});
|
|
452
451
|
|
|
453
|
-
|
|
454
|
-
const blockedIssue = createMockIssue({
|
|
452
|
+
const issueInBlockedRepo = createMockIssue({
|
|
455
453
|
url: 'https://github.com/user/repo/issues/101',
|
|
456
|
-
title: '
|
|
454
|
+
title: 'Issue in blocked repo',
|
|
457
455
|
labels: [],
|
|
458
456
|
status: 'Awaiting Workspace',
|
|
459
457
|
state: 'OPEN',
|
|
460
458
|
});
|
|
461
459
|
|
|
462
|
-
// Create a storyObjectMap with a workflow blocker story
|
|
463
460
|
const workflowBlockerMap: StoryObjectMap = new Map();
|
|
464
461
|
workflowBlockerMap.set('Workflow blocker', {
|
|
465
462
|
story: {
|
|
@@ -479,257 +476,14 @@ describe('StartPreparationUseCase', () => {
|
|
|
479
476
|
description: '',
|
|
480
477
|
},
|
|
481
478
|
storyIssue: null,
|
|
482
|
-
issues: [
|
|
479
|
+
issues: [issueInBlockedRepo],
|
|
483
480
|
});
|
|
484
481
|
|
|
485
482
|
mockProjectRepository.getByUrl.mockResolvedValue(mockProject);
|
|
486
483
|
mockIssueRepository.getStoryObjectMap.mockResolvedValue(workflowBlockerMap);
|
|
487
484
|
mockIssueRepository.getAllOpened.mockResolvedValueOnce([
|
|
488
485
|
blockerIssue,
|
|
489
|
-
|
|
490
|
-
]);
|
|
491
|
-
mockLocalCommandRunner.runCommand.mockResolvedValue({
|
|
492
|
-
stdout: '',
|
|
493
|
-
stderr: '',
|
|
494
|
-
exitCode: 0,
|
|
495
|
-
});
|
|
496
|
-
|
|
497
|
-
await useCase.run({
|
|
498
|
-
projectUrl: 'https://github.com/user/repo',
|
|
499
|
-
awaitingWorkspaceStatus: 'Awaiting Workspace',
|
|
500
|
-
preparationStatus: 'Preparation',
|
|
501
|
-
defaultAgentName: 'agent1',
|
|
502
|
-
maximumPreparingIssuesCount: null,
|
|
503
|
-
utilizationPercentageThreshold: 90,
|
|
504
|
-
allowedIssueAuthors: null,
|
|
505
|
-
});
|
|
506
|
-
|
|
507
|
-
// The blocked issue should be skipped (continue statement), but the blocker issue itself should be processed
|
|
508
|
-
// Since pop() returns last element first, blockedIssue is popped first and skipped,
|
|
509
|
-
// then blockerIssue is popped and processed
|
|
510
|
-
const blockerUpdateCalls = mockIssueRepository.update.mock.calls.filter(
|
|
511
|
-
(call) => call[0].url === 'https://github.com/user/repo/issues/100',
|
|
512
|
-
);
|
|
513
|
-
expect(blockerUpdateCalls).toHaveLength(1);
|
|
514
|
-
});
|
|
515
|
-
|
|
516
|
-
it('should process the blocker issue even when repository is blocked', async () => {
|
|
517
|
-
// Create a workflow blocker issue
|
|
518
|
-
const blockerIssue = createMockIssue({
|
|
519
|
-
url: 'https://github.com/user/repo/issues/100',
|
|
520
|
-
title: 'Blocker Issue',
|
|
521
|
-
labels: [],
|
|
522
|
-
status: 'Awaiting Workspace',
|
|
523
|
-
state: 'OPEN',
|
|
524
|
-
});
|
|
525
|
-
|
|
526
|
-
// Create a storyObjectMap with a workflow blocker story
|
|
527
|
-
const workflowBlockerMap2: StoryObjectMap = new Map();
|
|
528
|
-
workflowBlockerMap2.set('Workflow blocker', {
|
|
529
|
-
story: {
|
|
530
|
-
id: 'story-blocker',
|
|
531
|
-
name: 'Workflow blocker',
|
|
532
|
-
color: 'RED',
|
|
533
|
-
description: '',
|
|
534
|
-
},
|
|
535
|
-
storyIssue: null,
|
|
536
|
-
issues: [blockerIssue],
|
|
537
|
-
});
|
|
538
|
-
|
|
539
|
-
mockProjectRepository.getByUrl.mockResolvedValue(mockProject);
|
|
540
|
-
mockIssueRepository.getStoryObjectMap.mockResolvedValue(
|
|
541
|
-
workflowBlockerMap2,
|
|
542
|
-
);
|
|
543
|
-
mockIssueRepository.getAllOpened.mockResolvedValueOnce([blockerIssue]);
|
|
544
|
-
mockLocalCommandRunner.runCommand.mockResolvedValue({
|
|
545
|
-
stdout: '',
|
|
546
|
-
stderr: '',
|
|
547
|
-
exitCode: 0,
|
|
548
|
-
});
|
|
549
|
-
|
|
550
|
-
await useCase.run({
|
|
551
|
-
projectUrl: 'https://github.com/user/repo',
|
|
552
|
-
awaitingWorkspaceStatus: 'Awaiting Workspace',
|
|
553
|
-
preparationStatus: 'Preparation',
|
|
554
|
-
defaultAgentName: 'agent1',
|
|
555
|
-
maximumPreparingIssuesCount: null,
|
|
556
|
-
utilizationPercentageThreshold: 90,
|
|
557
|
-
allowedIssueAuthors: null,
|
|
558
|
-
});
|
|
559
|
-
|
|
560
|
-
// The blocker issue should be processed since it's the blocker itself
|
|
561
|
-
expect(mockIssueRepository.update.mock.calls).toHaveLength(1);
|
|
562
|
-
expect(mockIssueRepository.update.mock.calls[0][0]).toMatchObject({
|
|
563
|
-
url: 'https://github.com/user/repo/issues/100',
|
|
564
|
-
status: 'Preparation',
|
|
565
|
-
});
|
|
566
|
-
});
|
|
567
|
-
|
|
568
|
-
it('should collect blocker issues from multiple workflow blocker stories', async () => {
|
|
569
|
-
const blockerIssueRepo1 = createMockIssue({
|
|
570
|
-
url: 'https://github.com/user/repo1/issues/100',
|
|
571
|
-
title: 'Blocker Issue Repo1',
|
|
572
|
-
labels: [],
|
|
573
|
-
status: 'Awaiting Workspace',
|
|
574
|
-
state: 'OPEN',
|
|
575
|
-
});
|
|
576
|
-
|
|
577
|
-
const blockerIssueRepo2 = createMockIssue({
|
|
578
|
-
url: 'https://github.com/user/repo2/issues/200',
|
|
579
|
-
title: 'Blocker Issue Repo2',
|
|
580
|
-
labels: [],
|
|
581
|
-
status: 'Awaiting Workspace',
|
|
582
|
-
state: 'OPEN',
|
|
583
|
-
});
|
|
584
|
-
|
|
585
|
-
const blockedIssueRepo1 = createMockIssue({
|
|
586
|
-
url: 'https://github.com/user/repo1/issues/101',
|
|
587
|
-
title: 'Blocked Issue in Repo1',
|
|
588
|
-
labels: [],
|
|
589
|
-
status: 'Awaiting Workspace',
|
|
590
|
-
state: 'OPEN',
|
|
591
|
-
});
|
|
592
|
-
|
|
593
|
-
const blockedIssueRepo2 = createMockIssue({
|
|
594
|
-
url: 'https://github.com/user/repo2/issues/201',
|
|
595
|
-
title: 'Blocked Issue in Repo2',
|
|
596
|
-
labels: [],
|
|
597
|
-
status: 'Awaiting Workspace',
|
|
598
|
-
state: 'OPEN',
|
|
599
|
-
});
|
|
600
|
-
|
|
601
|
-
const unblockedIssue = createMockIssue({
|
|
602
|
-
url: 'https://github.com/user/repo3/issues/300',
|
|
603
|
-
title: 'Unblocked Issue',
|
|
604
|
-
labels: [],
|
|
605
|
-
status: 'Awaiting Workspace',
|
|
606
|
-
state: 'OPEN',
|
|
607
|
-
});
|
|
608
|
-
|
|
609
|
-
const multiBlockerMap: StoryObjectMap = new Map();
|
|
610
|
-
multiBlockerMap.set('Workflow blocker A', {
|
|
611
|
-
story: {
|
|
612
|
-
id: 'story-blocker-a',
|
|
613
|
-
name: 'Workflow blocker A',
|
|
614
|
-
color: 'RED',
|
|
615
|
-
description: '',
|
|
616
|
-
},
|
|
617
|
-
storyIssue: null,
|
|
618
|
-
issues: [blockerIssueRepo1],
|
|
619
|
-
});
|
|
620
|
-
multiBlockerMap.set('Workflow blocker B', {
|
|
621
|
-
story: {
|
|
622
|
-
id: 'story-blocker-b',
|
|
623
|
-
name: 'Workflow blocker B',
|
|
624
|
-
color: 'RED',
|
|
625
|
-
description: '',
|
|
626
|
-
},
|
|
627
|
-
storyIssue: null,
|
|
628
|
-
issues: [blockerIssueRepo2],
|
|
629
|
-
});
|
|
630
|
-
multiBlockerMap.set('Default Story', {
|
|
631
|
-
story: {
|
|
632
|
-
id: 'story-1',
|
|
633
|
-
name: 'Default Story',
|
|
634
|
-
color: 'GRAY',
|
|
635
|
-
description: '',
|
|
636
|
-
},
|
|
637
|
-
storyIssue: null,
|
|
638
|
-
issues: [blockedIssueRepo1, blockedIssueRepo2, unblockedIssue],
|
|
639
|
-
});
|
|
640
|
-
|
|
641
|
-
mockProjectRepository.getByUrl.mockResolvedValue(mockProject);
|
|
642
|
-
mockIssueRepository.getStoryObjectMap.mockResolvedValue(multiBlockerMap);
|
|
643
|
-
mockIssueRepository.getAllOpened.mockResolvedValueOnce([
|
|
644
|
-
blockerIssueRepo1,
|
|
645
|
-
blockerIssueRepo2,
|
|
646
|
-
blockedIssueRepo1,
|
|
647
|
-
blockedIssueRepo2,
|
|
648
|
-
unblockedIssue,
|
|
649
|
-
]);
|
|
650
|
-
mockLocalCommandRunner.runCommand.mockResolvedValue({
|
|
651
|
-
stdout: '',
|
|
652
|
-
stderr: '',
|
|
653
|
-
exitCode: 0,
|
|
654
|
-
});
|
|
655
|
-
|
|
656
|
-
await useCase.run({
|
|
657
|
-
projectUrl: 'https://github.com/user/repo',
|
|
658
|
-
awaitingWorkspaceStatus: 'Awaiting Workspace',
|
|
659
|
-
preparationStatus: 'Preparation',
|
|
660
|
-
defaultAgentName: 'agent1',
|
|
661
|
-
maximumPreparingIssuesCount: null,
|
|
662
|
-
utilizationPercentageThreshold: 90,
|
|
663
|
-
allowedIssueAuthors: null,
|
|
664
|
-
});
|
|
665
|
-
|
|
666
|
-
const updatedUrls = mockIssueRepository.update.mock.calls.map(
|
|
667
|
-
(call) => call[0].url,
|
|
668
|
-
);
|
|
669
|
-
expect(updatedUrls).toContain('https://github.com/user/repo1/issues/100');
|
|
670
|
-
expect(updatedUrls).toContain('https://github.com/user/repo2/issues/200');
|
|
671
|
-
expect(updatedUrls).toContain('https://github.com/user/repo3/issues/300');
|
|
672
|
-
expect(updatedUrls).not.toContain(
|
|
673
|
-
'https://github.com/user/repo1/issues/101',
|
|
674
|
-
);
|
|
675
|
-
expect(updatedUrls).not.toContain(
|
|
676
|
-
'https://github.com/user/repo2/issues/201',
|
|
677
|
-
);
|
|
678
|
-
});
|
|
679
|
-
|
|
680
|
-
it('should aggregate blocker URLs per orgRepo when multiple blocker issues exist in the same repo', async () => {
|
|
681
|
-
const blockerIssue1 = createMockIssue({
|
|
682
|
-
url: 'https://github.com/user/repo/issues/100',
|
|
683
|
-
title: 'Blocker Issue 1',
|
|
684
|
-
labels: [],
|
|
685
|
-
status: 'Awaiting Workspace',
|
|
686
|
-
state: 'OPEN',
|
|
687
|
-
});
|
|
688
|
-
|
|
689
|
-
const blockerIssue2 = createMockIssue({
|
|
690
|
-
url: 'https://github.com/user/repo/issues/101',
|
|
691
|
-
title: 'Blocker Issue 2',
|
|
692
|
-
labels: [],
|
|
693
|
-
status: 'Awaiting Workspace',
|
|
694
|
-
state: 'OPEN',
|
|
695
|
-
});
|
|
696
|
-
|
|
697
|
-
const nonBlockerIssue = createMockIssue({
|
|
698
|
-
url: 'https://github.com/user/repo/issues/102',
|
|
699
|
-
title: 'Non-Blocker Issue',
|
|
700
|
-
labels: [],
|
|
701
|
-
status: 'Awaiting Workspace',
|
|
702
|
-
state: 'OPEN',
|
|
703
|
-
});
|
|
704
|
-
|
|
705
|
-
const blockerMap: StoryObjectMap = new Map();
|
|
706
|
-
blockerMap.set('Workflow blocker', {
|
|
707
|
-
story: {
|
|
708
|
-
id: 'story-blocker',
|
|
709
|
-
name: 'Workflow blocker',
|
|
710
|
-
color: 'RED',
|
|
711
|
-
description: '',
|
|
712
|
-
},
|
|
713
|
-
storyIssue: null,
|
|
714
|
-
issues: [blockerIssue1, blockerIssue2],
|
|
715
|
-
});
|
|
716
|
-
blockerMap.set('Default Story', {
|
|
717
|
-
story: {
|
|
718
|
-
id: 'story-1',
|
|
719
|
-
name: 'Default Story',
|
|
720
|
-
color: 'GRAY',
|
|
721
|
-
description: '',
|
|
722
|
-
},
|
|
723
|
-
storyIssue: null,
|
|
724
|
-
issues: [nonBlockerIssue],
|
|
725
|
-
});
|
|
726
|
-
|
|
727
|
-
mockProjectRepository.getByUrl.mockResolvedValue(mockProject);
|
|
728
|
-
mockIssueRepository.getStoryObjectMap.mockResolvedValue(blockerMap);
|
|
729
|
-
mockIssueRepository.getAllOpened.mockResolvedValueOnce([
|
|
730
|
-
blockerIssue1,
|
|
731
|
-
blockerIssue2,
|
|
732
|
-
nonBlockerIssue,
|
|
486
|
+
issueInBlockedRepo,
|
|
733
487
|
]);
|
|
734
488
|
mockLocalCommandRunner.runCommand.mockResolvedValue({
|
|
735
489
|
stdout: '',
|
|
@@ -747,84 +501,12 @@ describe('StartPreparationUseCase', () => {
|
|
|
747
501
|
allowedIssueAuthors: null,
|
|
748
502
|
});
|
|
749
503
|
|
|
504
|
+
expect(mockIssueRepository.update.mock.calls).toHaveLength(2);
|
|
750
505
|
const updatedUrls = mockIssueRepository.update.mock.calls.map(
|
|
751
506
|
(call) => call[0].url,
|
|
752
507
|
);
|
|
753
508
|
expect(updatedUrls).toContain('https://github.com/user/repo/issues/100');
|
|
754
509
|
expect(updatedUrls).toContain('https://github.com/user/repo/issues/101');
|
|
755
|
-
expect(updatedUrls).not.toContain(
|
|
756
|
-
'https://github.com/user/repo/issues/102',
|
|
757
|
-
);
|
|
758
|
-
});
|
|
759
|
-
|
|
760
|
-
it('should handle workflow blocker story with undefined storyObject (|| [] fallback)', async () => {
|
|
761
|
-
// Create an awaiting issue that should be processed
|
|
762
|
-
const awaitingIssue = createMockIssue({
|
|
763
|
-
url: 'https://github.com/user/repo/issues/101',
|
|
764
|
-
title: 'Awaiting Issue',
|
|
765
|
-
labels: [],
|
|
766
|
-
status: 'Awaiting Workspace',
|
|
767
|
-
state: 'OPEN',
|
|
768
|
-
});
|
|
769
|
-
|
|
770
|
-
// Create a custom Map that has a 'Workflow blocker' key but returns undefined for get()
|
|
771
|
-
// This tests the || [] fallback branch
|
|
772
|
-
const customMap: StoryObjectMap = new Map();
|
|
773
|
-
customMap.set('Default Story', {
|
|
774
|
-
story: {
|
|
775
|
-
id: 'story-1',
|
|
776
|
-
name: 'Default Story',
|
|
777
|
-
color: 'GRAY',
|
|
778
|
-
description: '',
|
|
779
|
-
},
|
|
780
|
-
storyIssue: null,
|
|
781
|
-
issues: [awaitingIssue],
|
|
782
|
-
});
|
|
783
|
-
// Add the Workflow blocker key to the Map
|
|
784
|
-
customMap.set('Workflow blocker', {
|
|
785
|
-
story: {
|
|
786
|
-
id: 'story-blocker',
|
|
787
|
-
name: 'Workflow blocker',
|
|
788
|
-
color: 'RED',
|
|
789
|
-
description: '',
|
|
790
|
-
},
|
|
791
|
-
storyIssue: null,
|
|
792
|
-
issues: [],
|
|
793
|
-
});
|
|
794
|
-
// Override the get method to return undefined for 'Workflow blocker' key
|
|
795
|
-
const originalGet = customMap.get.bind(customMap);
|
|
796
|
-
customMap.get = (key: string) => {
|
|
797
|
-
if (key === 'Workflow blocker') {
|
|
798
|
-
return undefined;
|
|
799
|
-
}
|
|
800
|
-
return originalGet(key);
|
|
801
|
-
};
|
|
802
|
-
|
|
803
|
-
mockProjectRepository.getByUrl.mockResolvedValue(mockProject);
|
|
804
|
-
mockIssueRepository.getStoryObjectMap.mockResolvedValue(customMap);
|
|
805
|
-
mockIssueRepository.getAllOpened.mockResolvedValueOnce([awaitingIssue]);
|
|
806
|
-
mockLocalCommandRunner.runCommand.mockResolvedValue({
|
|
807
|
-
stdout: '',
|
|
808
|
-
stderr: '',
|
|
809
|
-
exitCode: 0,
|
|
810
|
-
});
|
|
811
|
-
|
|
812
|
-
await useCase.run({
|
|
813
|
-
projectUrl: 'https://github.com/user/repo',
|
|
814
|
-
awaitingWorkspaceStatus: 'Awaiting Workspace',
|
|
815
|
-
preparationStatus: 'Preparation',
|
|
816
|
-
defaultAgentName: 'agent1',
|
|
817
|
-
maximumPreparingIssuesCount: null,
|
|
818
|
-
utilizationPercentageThreshold: 90,
|
|
819
|
-
allowedIssueAuthors: null,
|
|
820
|
-
});
|
|
821
|
-
|
|
822
|
-
// The awaiting issue should be processed since there are no blockers (undefined returned empty array [])
|
|
823
|
-
expect(mockIssueRepository.update.mock.calls).toHaveLength(1);
|
|
824
|
-
expect(mockIssueRepository.update.mock.calls[0][0]).toMatchObject({
|
|
825
|
-
url: 'https://github.com/user/repo/issues/101',
|
|
826
|
-
status: 'Preparation',
|
|
827
|
-
});
|
|
828
510
|
});
|
|
829
511
|
|
|
830
512
|
it('should skip preparation when Claude usage is over 90%', async () => {
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { IssueRepository } from './adapter-interfaces/IssueRepository';
|
|
2
2
|
import { ProjectRepository } from './adapter-interfaces/ProjectRepository';
|
|
3
3
|
import { LocalCommandRunner } from './adapter-interfaces/LocalCommandRunner';
|
|
4
|
-
import { StoryObject, StoryObjectMap } from '../entities/StoryObjectMap';
|
|
5
4
|
import { ClaudeRepository } from './adapter-interfaces/ClaudeRepository';
|
|
6
5
|
|
|
7
6
|
export class StartPreparationUseCase {
|
|
@@ -59,9 +58,6 @@ export class StartPreparationUseCase {
|
|
|
59
58
|
await this.issueRepository.getStoryObjectMap(project);
|
|
60
59
|
const allIssues = await this.issueRepository.getAllOpened(project);
|
|
61
60
|
|
|
62
|
-
const repositoryBlockerIssues =
|
|
63
|
-
this.createWorkflowBockerIsues(storyObjectMap);
|
|
64
|
-
|
|
65
61
|
const awaitingWorkspaceIssues = Array.from(storyObjectMap.values())
|
|
66
62
|
.map((storyObject) => storyObject.issues)
|
|
67
63
|
.flat()
|
|
@@ -98,16 +94,6 @@ export class StartPreparationUseCase {
|
|
|
98
94
|
i++
|
|
99
95
|
) {
|
|
100
96
|
const issue = awaitingWorkspaceIssues[i];
|
|
101
|
-
const blockerIssueUrls: string[] =
|
|
102
|
-
repositoryBlockerIssues.find((blocker) =>
|
|
103
|
-
issue.url.includes(blocker.orgRepo),
|
|
104
|
-
)?.blockerIssueUrls || [];
|
|
105
|
-
if (
|
|
106
|
-
blockerIssueUrls.length > 0 &&
|
|
107
|
-
!blockerIssueUrls.includes(issue.url)
|
|
108
|
-
) {
|
|
109
|
-
continue;
|
|
110
|
-
}
|
|
111
97
|
if (issue.dependedIssueUrls.length > 0) {
|
|
112
98
|
continue;
|
|
113
99
|
}
|
|
@@ -144,39 +130,4 @@ export class StartPreparationUseCase {
|
|
|
144
130
|
updatedCurrentPreparationIssueCount++;
|
|
145
131
|
}
|
|
146
132
|
};
|
|
147
|
-
createWorkflowBockerIsues = (
|
|
148
|
-
storyObjectMap: StoryObjectMap,
|
|
149
|
-
): {
|
|
150
|
-
orgRepo: string;
|
|
151
|
-
blockerIssueUrls: string[];
|
|
152
|
-
}[] => {
|
|
153
|
-
const workflowBlockerStory: StoryObject['story']['name'][] = Array.from(
|
|
154
|
-
storyObjectMap.keys(),
|
|
155
|
-
).filter((storyName) =>
|
|
156
|
-
storyName.toLowerCase().includes('workflow blocker'),
|
|
157
|
-
);
|
|
158
|
-
if (workflowBlockerStory.length === 0) {
|
|
159
|
-
return [];
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
const aggregated = new Map<string, string[]>();
|
|
163
|
-
workflowBlockerStory.forEach((storyName) => {
|
|
164
|
-
const issues =
|
|
165
|
-
storyObjectMap
|
|
166
|
-
.get(storyName)
|
|
167
|
-
?.issues.filter((issue) => issue.state === 'OPEN') || [];
|
|
168
|
-
issues.forEach((issue) => {
|
|
169
|
-
const orgRepo = issue.url.split('/issues')[0].split('github.com/')[1];
|
|
170
|
-
const existing = aggregated.get(orgRepo) || [];
|
|
171
|
-
existing.push(issue.url);
|
|
172
|
-
aggregated.set(orgRepo, existing);
|
|
173
|
-
});
|
|
174
|
-
});
|
|
175
|
-
return Array.from(aggregated.entries()).map(
|
|
176
|
-
([orgRepo, blockerIssueUrls]) => ({
|
|
177
|
-
orgRepo,
|
|
178
|
-
blockerIssueUrls,
|
|
179
|
-
}),
|
|
180
|
-
);
|
|
181
|
-
};
|
|
182
133
|
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { IssueRepository } from './adapter-interfaces/IssueRepository';
|
|
2
2
|
import { ProjectRepository } from './adapter-interfaces/ProjectRepository';
|
|
3
3
|
import { LocalCommandRunner } from './adapter-interfaces/LocalCommandRunner';
|
|
4
|
-
import { StoryObjectMap } from '../entities/StoryObjectMap';
|
|
5
4
|
import { ClaudeRepository } from './adapter-interfaces/ClaudeRepository';
|
|
6
5
|
export declare class StartPreparationUseCase {
|
|
7
6
|
private readonly projectRepository;
|
|
@@ -19,9 +18,5 @@ export declare class StartPreparationUseCase {
|
|
|
19
18
|
utilizationPercentageThreshold: number;
|
|
20
19
|
allowedIssueAuthors: string[] | null;
|
|
21
20
|
}) => Promise<void>;
|
|
22
|
-
createWorkflowBockerIsues: (storyObjectMap: StoryObjectMap) => {
|
|
23
|
-
orgRepo: string;
|
|
24
|
-
blockerIssueUrls: string[];
|
|
25
|
-
}[];
|
|
26
21
|
}
|
|
27
22
|
//# sourceMappingURL=StartPreparationUseCase.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"StartPreparationUseCase.d.ts","sourceRoot":"","sources":["../../../src/domain/usecases/StartPreparationUseCase.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,sCAAsC,CAAC;AACvE,OAAO,EAAE,iBAAiB,EAAE,MAAM,wCAAwC,CAAC;AAC3E,OAAO,EAAE,kBAAkB,EAAE,MAAM,yCAAyC,CAAC;AAC7E,OAAO,
|
|
1
|
+
{"version":3,"file":"StartPreparationUseCase.d.ts","sourceRoot":"","sources":["../../../src/domain/usecases/StartPreparationUseCase.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,sCAAsC,CAAC;AACvE,OAAO,EAAE,iBAAiB,EAAE,MAAM,wCAAwC,CAAC;AAC3E,OAAO,EAAE,kBAAkB,EAAE,MAAM,yCAAyC,CAAC;AAC7E,OAAO,EAAE,gBAAgB,EAAE,MAAM,uCAAuC,CAAC;AAEzE,qBAAa,uBAAuB;IAEhC,OAAO,CAAC,QAAQ,CAAC,iBAAiB;IAIlC,OAAO,CAAC,QAAQ,CAAC,eAAe;IAIhC,OAAO,CAAC,QAAQ,CAAC,gBAAgB;IACjC,OAAO,CAAC,QAAQ,CAAC,kBAAkB;gBATlB,iBAAiB,EAAE,IAAI,CACtC,iBAAiB,EACjB,UAAU,GAAG,eAAe,CAC7B,EACgB,eAAe,EAAE,IAAI,CACpC,eAAe,EACf,cAAc,GAAG,mBAAmB,GAAG,QAAQ,CAChD,EACgB,gBAAgB,EAAE,IAAI,CAAC,gBAAgB,EAAE,UAAU,CAAC,EACpD,kBAAkB,EAAE,kBAAkB;IAGzD,GAAG,GAAU,QAAQ;QACnB,UAAU,EAAE,MAAM,CAAC;QACnB,uBAAuB,EAAE,MAAM,CAAC;QAChC,iBAAiB,EAAE,MAAM,CAAC;QAC1B,gBAAgB,EAAE,MAAM,CAAC;QACzB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,2BAA2B,EAAE,MAAM,GAAG,IAAI,CAAC;QAC3C,8BAA8B,EAAE,MAAM,CAAC;QACvC,mBAAmB,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;KACtC,KAAG,OAAO,CAAC,IAAI,CAAC,CAuGf;CACH"}
|