github-issue-tower-defence-management 1.116.2 → 1.116.5

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 (39) hide show
  1. package/.github/workflows/commit-lint.yml +1 -1
  2. package/.github/workflows/configs/commitlint.config.cjs +32 -0
  3. package/.github/workflows/umino-project.yml +9 -3
  4. package/CHANGELOG.md +14 -0
  5. package/README.md +2 -2
  6. package/bin/adapter/repositories/ProcClaudeLiveSessionRepository.js +18 -6
  7. package/bin/adapter/repositories/ProcClaudeLiveSessionRepository.js.map +1 -1
  8. package/bin/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.js +74 -49
  9. package/bin/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.js.map +1 -1
  10. package/bin/adapter/repositories/issue/githubRateLimitRetry.js +77 -0
  11. package/bin/adapter/repositories/issue/githubRateLimitRetry.js.map +1 -0
  12. package/bin/domain/usecases/LiveSessionOauthTokenSelectUseCase.js +6 -6
  13. package/bin/domain/usecases/LiveSessionOauthTokenSelectUseCase.js.map +1 -1
  14. package/bin/domain/usecases/OauthTokenSelectUseCase.js +1 -1
  15. package/bin/domain/usecases/OauthTokenSelectUseCase.js.map +1 -1
  16. package/package.json +1 -1
  17. package/renovate.json +1 -0
  18. package/src/adapter/entry-points/handlers/LiveSessionOauthTokenSelectHandler.test.ts +11 -11
  19. package/src/adapter/repositories/ProcClaudeLiveSessionRepository.test.ts +69 -7
  20. package/src/adapter/repositories/ProcClaudeLiveSessionRepository.ts +19 -8
  21. package/src/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.test.ts +111 -0
  22. package/src/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.ts +243 -193
  23. package/src/adapter/repositories/issue/githubRateLimitRetry.test.ts +167 -0
  24. package/src/adapter/repositories/issue/githubRateLimitRetry.ts +105 -0
  25. package/src/domain/usecases/LiveSessionOauthTokenSelectUseCase.test.ts +25 -3
  26. package/src/domain/usecases/LiveSessionOauthTokenSelectUseCase.ts +7 -7
  27. package/src/domain/usecases/OauthTokenSelectUseCase.test.ts +13 -4
  28. package/src/domain/usecases/OauthTokenSelectUseCase.ts +1 -1
  29. package/src/domain/usecases/adapter-interfaces/ClaudeLiveSessionRepository.ts +1 -1
  30. package/types/adapter/repositories/ProcClaudeLiveSessionRepository.d.ts +1 -0
  31. package/types/adapter/repositories/ProcClaudeLiveSessionRepository.d.ts.map +1 -1
  32. package/types/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.d.ts +4 -1
  33. package/types/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.d.ts.map +1 -1
  34. package/types/adapter/repositories/issue/githubRateLimitRetry.d.ts +10 -0
  35. package/types/adapter/repositories/issue/githubRateLimitRetry.d.ts.map +1 -0
  36. package/types/domain/usecases/OauthTokenSelectUseCase.d.ts +1 -1
  37. package/types/domain/usecases/OauthTokenSelectUseCase.d.ts.map +1 -1
  38. package/types/domain/usecases/adapter-interfaces/ClaudeLiveSessionRepository.d.ts +1 -1
  39. package/types/domain/usecases/adapter-interfaces/ClaudeLiveSessionRepository.d.ts.map +1 -1
@@ -593,6 +593,113 @@ describe('ApiV3CheerioRestIssueRepository', () => {
593
593
  });
594
594
  });
595
595
 
596
+ describe('rate-limit-aware retry on console operations', () => {
597
+ afterEach(() => {
598
+ jest.restoreAllMocks();
599
+ });
600
+
601
+ it('retries a transient 403 rate-limit response and resolves the operation after a success', async () => {
602
+ const fetchSpy = jest
603
+ .spyOn(global, 'fetch')
604
+ .mockResolvedValueOnce(
605
+ new Response(JSON.stringify({ message: 'API rate limit exceeded' }), {
606
+ status: 403,
607
+ headers: { 'x-ratelimit-remaining': '0' },
608
+ }),
609
+ )
610
+ .mockResolvedValueOnce(
611
+ new Response(JSON.stringify({ state: 'closed' }), { status: 200 }),
612
+ );
613
+
614
+ const { repository, sleep } = createApiV3CheerioRestIssueRepository();
615
+ await repository.closeIssueByUrl(
616
+ 'https://github.com/HiromiShikata/test-repository/issues/42',
617
+ 'completed',
618
+ );
619
+
620
+ expect(fetchSpy).toHaveBeenCalledTimes(2);
621
+ expect(sleep).toHaveBeenCalledTimes(1);
622
+ });
623
+
624
+ it('retries a transient 429 secondary-rate-limit response and resolves the operation after a success', async () => {
625
+ const fetchSpy = jest
626
+ .spyOn(global, 'fetch')
627
+ .mockResolvedValueOnce(
628
+ new Response(
629
+ JSON.stringify({
630
+ message: 'You have exceeded a secondary rate limit',
631
+ }),
632
+ { status: 429, headers: { 'retry-after': '1' } },
633
+ ),
634
+ )
635
+ .mockResolvedValueOnce(
636
+ new Response(JSON.stringify({ state: 'open', merged: false }), {
637
+ status: 200,
638
+ }),
639
+ );
640
+
641
+ const { repository, sleep } = createApiV3CheerioRestIssueRepository();
642
+ const result = await repository.getIssueOrPullRequestState(
643
+ 'https://github.com/HiromiShikata/test-repository/issues/42',
644
+ );
645
+
646
+ expect(result.state).toBe('open');
647
+ expect(fetchSpy).toHaveBeenCalledTimes(2);
648
+ expect(sleep).toHaveBeenCalledTimes(1);
649
+ });
650
+
651
+ it('does not retry a genuine permission 403 and surfaces a clear permission message', async () => {
652
+ const fetchSpy = jest.spyOn(global, 'fetch').mockResolvedValue(
653
+ new Response(
654
+ JSON.stringify({
655
+ message: 'Resource not accessible by integration',
656
+ }),
657
+ { status: 403, headers: { 'x-ratelimit-remaining': '4999' } },
658
+ ),
659
+ );
660
+
661
+ const { repository, sleep } = createApiV3CheerioRestIssueRepository();
662
+ await expect(
663
+ repository.closeIssueByUrl(
664
+ 'https://github.com/HiromiShikata/test-repository/issues/42',
665
+ 'completed',
666
+ ),
667
+ ).rejects.toThrow(
668
+ 'Failed to close issue https://github.com/HiromiShikata/test-repository/issues/42: HTTP 403 permission denied, the token cannot perform this operation Resource not accessible by integration',
669
+ );
670
+
671
+ expect(fetchSpy).toHaveBeenCalledTimes(1);
672
+ expect(sleep).not.toHaveBeenCalled();
673
+ });
674
+
675
+ it('surfaces a clear rate-limit message including the reset time after the bounded retries are exhausted', async () => {
676
+ const resetEpochSeconds = 1700000000;
677
+ const fetchSpy = jest.spyOn(global, 'fetch').mockResolvedValue(
678
+ new Response(JSON.stringify({ message: 'API rate limit exceeded' }), {
679
+ status: 403,
680
+ headers: {
681
+ 'x-ratelimit-remaining': '0',
682
+ 'x-ratelimit-reset': String(resetEpochSeconds),
683
+ },
684
+ }),
685
+ );
686
+
687
+ const { repository, sleep } = createApiV3CheerioRestIssueRepository();
688
+ const expectedResetIso = new Date(resetEpochSeconds * 1000).toISOString();
689
+ await expect(
690
+ repository.closeIssueByUrl(
691
+ 'https://github.com/HiromiShikata/test-repository/issues/42',
692
+ 'completed',
693
+ ),
694
+ ).rejects.toThrow(
695
+ `Failed to close issue https://github.com/HiromiShikata/test-repository/issues/42: HTTP 403 GitHub rate limit exceeded, please retry shortly (resets at ${expectedResetIso})`,
696
+ );
697
+
698
+ expect(fetchSpy).toHaveBeenCalledTimes(4);
699
+ expect(sleep).toHaveBeenCalledTimes(3);
700
+ });
701
+ });
702
+
596
703
  describe('requestChangesWithInlineComment', () => {
597
704
  afterEach(() => {
598
705
  jest.restoreAllMocks();
@@ -1636,6 +1743,7 @@ describe('ApiV3CheerioRestIssueRepository', () => {
1636
1743
  const graphqlProjectItemRepository = mock<GraphqlProjectItemRepository>();
1637
1744
  const localStorageCacheRepository = mock<LocalStorageCacheRepository>();
1638
1745
  const localStorageRepository = mock<LocalStorageRepository>();
1746
+ const sleep = jest.fn().mockResolvedValue(undefined);
1639
1747
 
1640
1748
  const repository = new ApiV3CheerioRestIssueRepository(
1641
1749
  apiV3IssueRepository,
@@ -1643,6 +1751,8 @@ describe('ApiV3CheerioRestIssueRepository', () => {
1643
1751
  graphqlProjectItemRepository,
1644
1752
  localStorageCacheRepository,
1645
1753
  localStorageRepository,
1754
+ 'dummy',
1755
+ sleep,
1646
1756
  );
1647
1757
  restIssueRepository.getIssue.mockResolvedValue({
1648
1758
  labels: [],
@@ -1659,6 +1769,7 @@ describe('ApiV3CheerioRestIssueRepository', () => {
1659
1769
  restIssueRepository,
1660
1770
  graphqlProjectItemRepository,
1661
1771
  localStorageCacheRepository,
1772
+ sleep,
1662
1773
  };
1663
1774
  };
1664
1775
  });