github-issue-tower-defence-management 2.65.1 → 2.67.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 (42) hide show
  1. package/README.md +3 -0
  2. package/bin/adapter/entry-points/cli/index.js +7 -0
  3. package/bin/adapter/entry-points/cli/index.js.map +1 -1
  4. package/bin/adapter/entry-points/cli/projectConfig.js +2 -0
  5. package/bin/adapter/entry-points/cli/projectConfig.js.map +1 -1
  6. package/bin/adapter/repositories/GitHubIssueCommentRepository.js +9 -2
  7. package/bin/adapter/repositories/GitHubIssueCommentRepository.js.map +1 -1
  8. package/bin/adapter/repositories/NodeTmuxSessionRepository.js +72 -0
  9. package/bin/adapter/repositories/NodeTmuxSessionRepository.js.map +1 -1
  10. package/bin/adapter/repositories/githubRestClient.js +87 -2
  11. package/bin/adapter/repositories/githubRestClient.js.map +1 -1
  12. package/bin/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.js +89 -59
  13. package/bin/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.js.map +1 -1
  14. package/bin/adapter/repositories/issue/githubRateLimitRetry.js +8 -2
  15. package/bin/adapter/repositories/issue/githubRateLimitRetry.js.map +1 -1
  16. package/bin/domain/usecases/NotifyFinishedIssuePreparationUseCase.js +2 -0
  17. package/bin/domain/usecases/NotifyFinishedIssuePreparationUseCase.js.map +1 -1
  18. package/bin/domain/usecases/RevertNotReadyReviewQueueIssueUseCase.js +3 -1
  19. package/bin/domain/usecases/RevertNotReadyReviewQueueIssueUseCase.js.map +1 -1
  20. package/bin/domain/usecases/RevertOrphanedPreparationUseCase.js +2 -0
  21. package/bin/domain/usecases/RevertOrphanedPreparationUseCase.js.map +1 -1
  22. package/bin/domain/usecases/resolveNextStepAgentDispatchRepetition.js +31 -1
  23. package/bin/domain/usecases/resolveNextStepAgentDispatchRepetition.js.map +1 -1
  24. package/package.json +1 -1
  25. package/types/adapter/entry-points/cli/index.d.ts.map +1 -1
  26. package/types/adapter/entry-points/cli/projectConfig.d.ts +1 -0
  27. package/types/adapter/entry-points/cli/projectConfig.d.ts.map +1 -1
  28. package/types/adapter/repositories/GitHubIssueCommentRepository.d.ts.map +1 -1
  29. package/types/adapter/repositories/NodeTmuxSessionRepository.d.ts +1 -0
  30. package/types/adapter/repositories/NodeTmuxSessionRepository.d.ts.map +1 -1
  31. package/types/adapter/repositories/githubRestClient.d.ts +6 -0
  32. package/types/adapter/repositories/githubRestClient.d.ts.map +1 -1
  33. package/types/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.d.ts.map +1 -1
  34. package/types/adapter/repositories/issue/githubRateLimitRetry.d.ts +5 -1
  35. package/types/adapter/repositories/issue/githubRateLimitRetry.d.ts.map +1 -1
  36. package/types/domain/usecases/NotifyFinishedIssuePreparationUseCase.d.ts +1 -0
  37. package/types/domain/usecases/NotifyFinishedIssuePreparationUseCase.d.ts.map +1 -1
  38. package/types/domain/usecases/RevertNotReadyReviewQueueIssueUseCase.d.ts.map +1 -1
  39. package/types/domain/usecases/RevertOrphanedPreparationUseCase.d.ts +1 -0
  40. package/types/domain/usecases/RevertOrphanedPreparationUseCase.d.ts.map +1 -1
  41. package/types/domain/usecases/resolveNextStepAgentDispatchRepetition.d.ts +2 -0
  42. package/types/domain/usecases/resolveNextStepAgentDispatchRepetition.d.ts.map +1 -1
@@ -8,6 +8,8 @@ const githubGraphqlClient_1 = require("../githubGraphqlClient");
8
8
  const utils_1 = require("../utils");
9
9
  const localStorageCacheDirectory_1 = require("../localStorageCacheDirectory");
10
10
  const githubRateLimitRetry_1 = require("./githubRateLimitRetry");
11
+ const githubRestClient_1 = require("../githubRestClient");
12
+ const githubSecondaryRateLimitBreaker_1 = require("./githubSecondaryRateLimitBreaker");
11
13
  exports.FULL_ISSUE_FETCH_INTERVAL_MS = 60 * 60 * 1000;
12
14
  exports.INCREMENTAL_FETCH_SKEW_BUFFER_MS = 5 * 60 * 1000;
13
15
  exports.REQUIRED_CHECKS_CACHE_TTL_MS = 10 * 60 * 1000;
@@ -238,14 +240,14 @@ class ApiV3CheerioRestIssueRepository extends BaseGitHubRepository_1.BaseGitHubR
238
240
  this.getAllIssuesRefreshMemo = new Map();
239
241
  this.lastIssuesFetchedAtByProjectId = new Map();
240
242
  this.getLastIssuesFetchedAt = (projectId) => this.lastIssuesFetchedAtByProjectId.get(projectId) ?? null;
241
- this.fetchWithRateLimitRetry = (request) => (0, githubRateLimitRetry_1.fetchWithGitHubRateLimitRetry)(request, this.sleep);
243
+ this.fetchWithRateLimitRetry = (request, requestInfo = {}) => (0, githubRateLimitRetry_1.fetchWithGitHubRateLimitRetry)(request, this.sleep, Date.now, false, false, (0, githubSecondaryRateLimitBreaker_1.secondaryRateLimitStateFilePath)(), requestInfo);
242
244
  /**
243
245
  * Variant for content-creating requests (POST / PUT / PATCH / DELETE).
244
246
  * Checks the shared secondary rate limit circuit breaker before issuing
245
247
  * the request so that a block discovered by one process immediately
246
248
  * silences all other processes on the host.
247
249
  */
248
- this.fetchWithRateLimitRetryForWrite = (request) => (0, githubRateLimitRetry_1.fetchWithGitHubRateLimitRetry)(request, this.sleep, Date.now, false, true);
250
+ this.fetchWithRateLimitRetryForWrite = (request, requestInfo = {}) => (0, githubRateLimitRetry_1.fetchWithGitHubRateLimitRetry)(request, this.sleep, Date.now, false, true, (0, githubSecondaryRateLimitBreaker_1.secondaryRateLimitStateFilePath)(), requestInfo);
249
251
  this.throwGitHubError = async (prefix, response) => {
250
252
  const bodyText = await response.clone().text();
251
253
  const isRateLimit = (0, githubRateLimitRetry_1.hasRateLimitSignals)(response.status, response.headers, bodyText);
@@ -769,13 +771,14 @@ class ApiV3CheerioRestIssueRepository extends BaseGitHubRepository_1.BaseGitHubR
769
771
  const repoSegment = encodeURIComponent(repo);
770
772
  const branchSegment = encodeURIComponent(branch);
771
773
  const requiredCheckNamesSet = new Set();
772
- const rulesResponse = await this.fetchWithRateLimitRetry(() => fetch(`https://api.github.com/repos/${ownerSegment}/${repoSegment}/rules/branches/${branchSegment}?per_page=100`, {
774
+ const rulesUrl = `https://api.github.com/repos/${ownerSegment}/${repoSegment}/rules/branches/${branchSegment}?per_page=100`;
775
+ const rulesResponse = await this.fetchWithRateLimitRetry(() => fetch(rulesUrl, {
773
776
  method: 'GET',
774
777
  headers: {
775
778
  Authorization: `Bearer ${this.ghToken}`,
776
779
  Accept: 'application/vnd.github+json',
777
780
  },
778
- }));
781
+ }), { method: 'GET', path: (0, githubRestClient_1.sanitizeRestPath)(rulesUrl) });
779
782
  if (rulesResponse.ok) {
780
783
  const rulesBody = await rulesResponse.json();
781
784
  if (!isBranchRulesResponse(rulesBody)) {
@@ -797,13 +800,14 @@ class ApiV3CheerioRestIssueRepository extends BaseGitHubRepository_1.BaseGitHubR
797
800
  const reason = await this.formatGitHubErrorWithStatus(rulesResponse);
798
801
  throw new Error(`Failed to fetch branch rules for ${owner}/${repo}/${branch}: ${reason}`);
799
802
  }
800
- const branchResponse = await this.fetchWithRateLimitRetry(() => fetch(`https://api.github.com/repos/${ownerSegment}/${repoSegment}/branches/${branchSegment}`, {
803
+ const branchUrl = `https://api.github.com/repos/${ownerSegment}/${repoSegment}/branches/${branchSegment}`;
804
+ const branchResponse = await this.fetchWithRateLimitRetry(() => fetch(branchUrl, {
801
805
  method: 'GET',
802
806
  headers: {
803
807
  Authorization: `Bearer ${this.ghToken}`,
804
808
  Accept: 'application/vnd.github+json',
805
809
  },
806
- }));
810
+ }), { method: 'GET', path: (0, githubRestClient_1.sanitizeRestPath)(branchUrl) });
807
811
  if (branchResponse.ok) {
808
812
  const branchBody = await branchResponse.json();
809
813
  if (!isBranchDetailResponse(branchBody)) {
@@ -852,13 +856,14 @@ class ApiV3CheerioRestIssueRepository extends BaseGitHubRepository_1.BaseGitHubR
852
856
  const ownerSegment = encodeURIComponent(owner);
853
857
  const repoSegment = encodeURIComponent(repo);
854
858
  const shaSegment = encodeURIComponent(commitSha);
855
- const checkSuitesResponse = await this.fetchWithRateLimitRetry(() => fetch(`https://api.github.com/repos/${ownerSegment}/${repoSegment}/commits/${shaSegment}/check-suites`, {
859
+ const checkSuitesUrl = `https://api.github.com/repos/${ownerSegment}/${repoSegment}/commits/${shaSegment}/check-suites`;
860
+ const checkSuitesResponse = await this.fetchWithRateLimitRetry(() => fetch(checkSuitesUrl, {
856
861
  method: 'GET',
857
862
  headers: {
858
863
  Authorization: `Bearer ${this.ghToken}`,
859
864
  Accept: 'application/vnd.github+json',
860
865
  },
861
- }));
866
+ }), { method: 'GET', path: (0, githubRestClient_1.sanitizeRestPath)(checkSuitesUrl) });
862
867
  if (!checkSuitesResponse.ok) {
863
868
  if (checkSuitesResponse.status === 404) {
864
869
  console.warn(`ApiV3CheerioRestIssueRepository: commits/${commitSha}/check-suites returned 404 for ${owner}/${repo}, treating as no check runs.`);
@@ -877,13 +882,14 @@ class ApiV3CheerioRestIssueRepository extends BaseGitHubRepository_1.BaseGitHubR
877
882
  let page = 1;
878
883
  let hasMore = true;
879
884
  while (hasMore) {
880
- const runsResponse = await this.fetchWithRateLimitRetry(() => fetch(`https://api.github.com/repos/${ownerSegment}/${repoSegment}/check-suites/${suite.id}/check-runs?per_page=${perPage}&page=${page}`, {
885
+ const runsUrl = `https://api.github.com/repos/${ownerSegment}/${repoSegment}/check-suites/${suite.id}/check-runs?per_page=${perPage}&page=${page}`;
886
+ const runsResponse = await this.fetchWithRateLimitRetry(() => fetch(runsUrl, {
881
887
  method: 'GET',
882
888
  headers: {
883
889
  Authorization: `Bearer ${this.ghToken}`,
884
890
  Accept: 'application/vnd.github+json',
885
891
  },
886
- }));
892
+ }), { method: 'GET', path: (0, githubRestClient_1.sanitizeRestPath)(runsUrl) });
887
893
  if (!runsResponse.ok) {
888
894
  const reason = await this.formatGitHubErrorWithStatus(runsResponse);
889
895
  throw new Error(`Failed to fetch check runs for suite ${suite.id} in ${owner}/${repo}: ${reason}`);
@@ -936,7 +942,8 @@ class ApiV3CheerioRestIssueRepository extends BaseGitHubRepository_1.BaseGitHubR
936
942
  if (diskCache?.etag) {
937
943
  page1Headers['If-None-Match'] = diskCache.etag;
938
944
  }
939
- const page1Response = await this.fetchWithRateLimitRetry(() => fetch(`https://api.github.com/repos/${ownerSegment}/${repoSegment}/commits/${shaSegment}/check-runs?per_page=100&page=1`, { method: 'GET', headers: page1Headers }));
945
+ const page1Url = `https://api.github.com/repos/${ownerSegment}/${repoSegment}/commits/${shaSegment}/check-runs?per_page=100&page=1`;
946
+ const page1Response = await this.fetchWithRateLimitRetry(() => fetch(page1Url, { method: 'GET', headers: page1Headers }), { method: 'GET', path: (0, githubRestClient_1.sanitizeRestPath)(page1Url) });
940
947
  if (!page1Response.ok &&
941
948
  (0, githubRateLimitRetry_1.hasRateLimitSignals)(page1Response.status, page1Response.headers, await page1Response.clone().text())) {
942
949
  if (diskCache) {
@@ -982,13 +989,14 @@ class ApiV3CheerioRestIssueRepository extends BaseGitHubRepository_1.BaseGitHubR
982
989
  let page = 2;
983
990
  let hasMore = page1Body.check_runs.length >= perPage && perPage < page1Body.total_count;
984
991
  while (hasMore) {
985
- const pageResponse = await this.fetchWithRateLimitRetry(() => fetch(`https://api.github.com/repos/${ownerSegment}/${repoSegment}/commits/${shaSegment}/check-runs?per_page=${perPage}&page=${page}`, {
992
+ const pageUrl = `https://api.github.com/repos/${ownerSegment}/${repoSegment}/commits/${shaSegment}/check-runs?per_page=${perPage}&page=${page}`;
993
+ const pageResponse = await this.fetchWithRateLimitRetry(() => fetch(pageUrl, {
986
994
  method: 'GET',
987
995
  headers: {
988
996
  Authorization: `Bearer ${this.ghToken}`,
989
997
  Accept: 'application/vnd.github+json',
990
998
  },
991
- }));
999
+ }), { method: 'GET', path: (0, githubRestClient_1.sanitizeRestPath)(pageUrl) });
992
1000
  if (!pageResponse.ok &&
993
1001
  (0, githubRateLimitRetry_1.hasRateLimitSignals)(pageResponse.status, pageResponse.headers, await pageResponse.clone().text())) {
994
1002
  if (diskCache) {
@@ -1033,10 +1041,11 @@ class ApiV3CheerioRestIssueRepository extends BaseGitHubRepository_1.BaseGitHubR
1033
1041
  if (diskCache?.combinedStatusEtag) {
1034
1042
  combinedStatusHeaders['If-None-Match'] = diskCache.combinedStatusEtag;
1035
1043
  }
1036
- const combinedStatusResponse = await this.fetchWithRateLimitRetry(() => fetch(`https://api.github.com/repos/${ownerSegment}/${repoSegment}/commits/${shaSegment}/status?per_page=100`, {
1044
+ const combinedStatusUrl = `https://api.github.com/repos/${ownerSegment}/${repoSegment}/commits/${shaSegment}/status?per_page=100`;
1045
+ const combinedStatusResponse = await this.fetchWithRateLimitRetry(() => fetch(combinedStatusUrl, {
1037
1046
  method: 'GET',
1038
1047
  headers: combinedStatusHeaders,
1039
- }));
1048
+ }), { method: 'GET', path: (0, githubRestClient_1.sanitizeRestPath)(combinedStatusUrl) });
1040
1049
  if (!combinedStatusResponse.ok &&
1041
1050
  (0, githubRateLimitRetry_1.hasRateLimitSignals)(combinedStatusResponse.status, combinedStatusResponse.headers, await combinedStatusResponse.clone().text())) {
1042
1051
  if (diskCache) {
@@ -1536,13 +1545,14 @@ class ApiV3CheerioRestIssueRepository extends BaseGitHubRepository_1.BaseGitHubR
1536
1545
  if (attempt > 0) {
1537
1546
  await this.sleep(retryDelayMilliseconds);
1538
1547
  }
1539
- const response = await this.fetchWithRateLimitRetry(() => fetch(`https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/pulls/${prNumber}`, {
1548
+ const prStatusUrl = `https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/pulls/${prNumber}`;
1549
+ const response = await this.fetchWithRateLimitRetry(() => fetch(prStatusUrl, {
1540
1550
  method: 'GET',
1541
1551
  headers: {
1542
1552
  Authorization: `Bearer ${this.ghToken}`,
1543
1553
  Accept: 'application/vnd.github+json',
1544
1554
  },
1545
- }));
1555
+ }), { method: 'GET', path: (0, githubRestClient_1.sanitizeRestPath)(prStatusUrl) });
1546
1556
  if (response.status === 404) {
1547
1557
  return null;
1548
1558
  }
@@ -1747,14 +1757,15 @@ class ApiV3CheerioRestIssueRepository extends BaseGitHubRepository_1.BaseGitHubR
1747
1757
  };
1748
1758
  this.closePullRequest = async (prUrl) => {
1749
1759
  const { owner, repo, issueNumber: prNumber } = this.parseIssueUrl(prUrl);
1750
- const response = await this.fetchWithRateLimitRetryForWrite(() => fetch(`https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/pulls/${prNumber}`, {
1760
+ const closePrUrl = `https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/pulls/${prNumber}`;
1761
+ const response = await this.fetchWithRateLimitRetryForWrite(() => fetch(closePrUrl, {
1751
1762
  method: 'PATCH',
1752
1763
  headers: {
1753
1764
  Authorization: `Bearer ${this.ghToken}`,
1754
1765
  'Content-Type': 'application/json',
1755
1766
  },
1756
1767
  body: JSON.stringify({ state: 'closed' }),
1757
- }));
1768
+ }), { method: 'PATCH', path: (0, githubRestClient_1.sanitizeRestPath)(closePrUrl) });
1758
1769
  if (!response.ok) {
1759
1770
  const reason = await this.formatGitHubErrorWithStatus(response);
1760
1771
  throw new Error(`Failed to close PR ${prUrl}: ${reason}`);
@@ -1764,14 +1775,15 @@ class ApiV3CheerioRestIssueRepository extends BaseGitHubRepository_1.BaseGitHubR
1764
1775
  const { owner, repo, issueNumber } = this.parseIssueUrl(issueUrl);
1765
1776
  const ownerSegment = encodeURIComponent(owner);
1766
1777
  const repoSegment = encodeURIComponent(repo);
1767
- const response = await this.fetchWithRateLimitRetryForWrite(() => fetch(`https://api.github.com/repos/${ownerSegment}/${repoSegment}/issues/${issueNumber}`, {
1778
+ const closeIssueUrl = `https://api.github.com/repos/${ownerSegment}/${repoSegment}/issues/${issueNumber}`;
1779
+ const response = await this.fetchWithRateLimitRetryForWrite(() => fetch(closeIssueUrl, {
1768
1780
  method: 'PATCH',
1769
1781
  headers: {
1770
1782
  Authorization: `Bearer ${this.ghToken}`,
1771
1783
  'Content-Type': 'application/json',
1772
1784
  },
1773
1785
  body: JSON.stringify({ state: 'closed', state_reason: stateReason }),
1774
- }));
1786
+ }), { method: 'PATCH', path: (0, githubRestClient_1.sanitizeRestPath)(closeIssueUrl) });
1775
1787
  if (!response.ok) {
1776
1788
  const reason = await this.formatGitHubErrorWithStatus(response);
1777
1789
  throw new Error(`Failed to close issue ${issueUrl}: ${reason}`);
@@ -1784,13 +1796,14 @@ class ApiV3CheerioRestIssueRepository extends BaseGitHubRepository_1.BaseGitHubR
1784
1796
  let page = 1;
1785
1797
  let hasMore = true;
1786
1798
  while (hasMore) {
1787
- const response = await this.fetchWithRateLimitRetry(() => fetch(`https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/pulls/${prNumber}/files?per_page=${perPage}&page=${page}`, {
1799
+ const prFilesUrl = `https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/pulls/${prNumber}/files?per_page=${perPage}&page=${page}`;
1800
+ const response = await this.fetchWithRateLimitRetry(() => fetch(prFilesUrl, {
1788
1801
  method: 'GET',
1789
1802
  headers: {
1790
1803
  Authorization: `Bearer ${this.ghToken}`,
1791
1804
  Accept: 'application/vnd.github+json',
1792
1805
  },
1793
- }));
1806
+ }), { method: 'GET', path: (0, githubRestClient_1.sanitizeRestPath)(prFilesUrl) });
1794
1807
  if (!response.ok) {
1795
1808
  const reason = await this.formatGitHubErrorWithStatus(response);
1796
1809
  throw new Error(`Failed to fetch changed files for PR ${prUrl}: ${reason}`);
@@ -1818,7 +1831,7 @@ class ApiV3CheerioRestIssueRepository extends BaseGitHubRepository_1.BaseGitHubR
1818
1831
  Authorization: `Bearer ${this.ghToken}`,
1819
1832
  Accept: 'application/vnd.github+json',
1820
1833
  },
1821
- }));
1834
+ }), { method: 'GET', path: '/user' });
1822
1835
  if (!response.ok) {
1823
1836
  const reason = await this.formatGitHubErrorWithStatus(response);
1824
1837
  throw new Error(`Failed to fetch authenticated user: ${reason}`);
@@ -1831,7 +1844,8 @@ class ApiV3CheerioRestIssueRepository extends BaseGitHubRepository_1.BaseGitHubR
1831
1844
  };
1832
1845
  this.approvePullRequest = async (prUrl) => {
1833
1846
  const { owner, repo, issueNumber: prNumber } = this.parseIssueUrl(prUrl);
1834
- const response = await this.fetchWithRateLimitRetryForWrite(() => fetch(`https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/pulls/${prNumber}/reviews`, {
1847
+ const approveUrl = `https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/pulls/${prNumber}/reviews`;
1848
+ const response = await this.fetchWithRateLimitRetryForWrite(() => fetch(approveUrl, {
1835
1849
  method: 'POST',
1836
1850
  headers: {
1837
1851
  Authorization: `Bearer ${this.ghToken}`,
@@ -1839,7 +1853,7 @@ class ApiV3CheerioRestIssueRepository extends BaseGitHubRepository_1.BaseGitHubR
1839
1853
  Accept: 'application/vnd.github+json',
1840
1854
  },
1841
1855
  body: JSON.stringify({ event: 'APPROVE' }),
1842
- }));
1856
+ }), { method: 'POST', path: (0, githubRestClient_1.sanitizeRestPath)(approveUrl) });
1843
1857
  if (!response.ok) {
1844
1858
  const reason = await this.formatGitHubErrorWithStatus(response);
1845
1859
  throw new Error(`Failed to approve PR ${prUrl}: ${reason}`);
@@ -1857,7 +1871,7 @@ class ApiV3CheerioRestIssueRepository extends BaseGitHubRepository_1.BaseGitHubR
1857
1871
  method: 'PUT',
1858
1872
  headers,
1859
1873
  body: JSON.stringify(mergeMethod ? { merge_method: mergeMethod } : {}),
1860
- }));
1874
+ }), { method: 'PUT', path: (0, githubRestClient_1.sanitizeRestPath)(mergeUrl) });
1861
1875
  const response = await mergeWith();
1862
1876
  if (response.ok) {
1863
1877
  return;
@@ -1877,12 +1891,13 @@ class ApiV3CheerioRestIssueRepository extends BaseGitHubRepository_1.BaseGitHubR
1877
1891
  throw new Error(`Failed to merge PR ${prUrl}: ${reason}`);
1878
1892
  };
1879
1893
  this.resolveAllowedMergeMethod = async (owner, repo) => {
1880
- const response = await this.fetchWithRateLimitRetry(() => fetch(`https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}`, {
1894
+ const repoUrl = `https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}`;
1895
+ const response = await this.fetchWithRateLimitRetry(() => fetch(repoUrl, {
1881
1896
  headers: {
1882
1897
  Authorization: `Bearer ${this.ghToken}`,
1883
1898
  Accept: 'application/vnd.github+json',
1884
1899
  },
1885
- }));
1900
+ }), { method: 'GET', path: (0, githubRestClient_1.sanitizeRestPath)(repoUrl) });
1886
1901
  if (!response.ok) {
1887
1902
  return null;
1888
1903
  }
@@ -1917,7 +1932,8 @@ class ApiV3CheerioRestIssueRepository extends BaseGitHubRepository_1.BaseGitHubR
1917
1932
  body: commentBody,
1918
1933
  comments: [inlineComment],
1919
1934
  };
1920
- const response = await this.fetchWithRateLimitRetryForWrite(() => fetch(`https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/pulls/${prNumber}/reviews`, {
1935
+ const reviewUrl = `https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/pulls/${prNumber}/reviews`;
1936
+ const response = await this.fetchWithRateLimitRetryForWrite(() => fetch(reviewUrl, {
1921
1937
  method: 'POST',
1922
1938
  headers: {
1923
1939
  Authorization: `Bearer ${this.ghToken}`,
@@ -1925,7 +1941,7 @@ class ApiV3CheerioRestIssueRepository extends BaseGitHubRepository_1.BaseGitHubR
1925
1941
  Accept: 'application/vnd.github+json',
1926
1942
  },
1927
1943
  body: JSON.stringify(reviewBody),
1928
- }));
1944
+ }), { method: 'POST', path: (0, githubRestClient_1.sanitizeRestPath)(reviewUrl) });
1929
1945
  if (!response.ok) {
1930
1946
  const reason = await this.formatGitHubErrorWithStatus(response);
1931
1947
  if (response.status === 422 &&
@@ -1946,13 +1962,14 @@ class ApiV3CheerioRestIssueRepository extends BaseGitHubRepository_1.BaseGitHubR
1946
1962
  this.fetchPullRequestHeadSha = async (owner, repo, prNumber, prUrl) => {
1947
1963
  const ownerSegment = encodeURIComponent(owner);
1948
1964
  const repoSegment = encodeURIComponent(repo);
1949
- const response = await this.fetchWithRateLimitRetry(() => fetch(`https://api.github.com/repos/${ownerSegment}/${repoSegment}/pulls/${prNumber}`, {
1965
+ const headShaUrl = `https://api.github.com/repos/${ownerSegment}/${repoSegment}/pulls/${prNumber}`;
1966
+ const response = await this.fetchWithRateLimitRetry(() => fetch(headShaUrl, {
1950
1967
  method: 'GET',
1951
1968
  headers: {
1952
1969
  Authorization: `Bearer ${this.ghToken}`,
1953
1970
  Accept: 'application/vnd.github+json',
1954
1971
  },
1955
- }));
1972
+ }), { method: 'GET', path: (0, githubRestClient_1.sanitizeRestPath)(headShaUrl) });
1956
1973
  if (!response.ok) {
1957
1974
  const reason = await this.formatGitHubErrorWithStatus(response);
1958
1975
  throw new Error(`Failed to fetch head commit for PR ${prUrl}: ${reason}`);
@@ -1970,7 +1987,8 @@ class ApiV3CheerioRestIssueRepository extends BaseGitHubRepository_1.BaseGitHubR
1970
1987
  const commitId = await this.fetchPullRequestHeadSha(owner, repo, prNumber, prUrl);
1971
1988
  const ownerSegment = encodeURIComponent(owner);
1972
1989
  const repoSegment = encodeURIComponent(repo);
1973
- const response = await this.fetchWithRateLimitRetryForWrite(() => fetch(`https://api.github.com/repos/${ownerSegment}/${repoSegment}/pulls/${prNumber}/comments`, {
1990
+ const reviewCommentUrl = `https://api.github.com/repos/${ownerSegment}/${repoSegment}/pulls/${prNumber}/comments`;
1991
+ const response = await this.fetchWithRateLimitRetryForWrite(() => fetch(reviewCommentUrl, {
1974
1992
  method: 'POST',
1975
1993
  headers: {
1976
1994
  Authorization: `Bearer ${this.ghToken}`,
@@ -1984,7 +2002,7 @@ class ApiV3CheerioRestIssueRepository extends BaseGitHubRepository_1.BaseGitHubR
1984
2002
  line,
1985
2003
  side,
1986
2004
  }),
1987
- }));
2005
+ }), { method: 'POST', path: (0, githubRestClient_1.sanitizeRestPath)(reviewCommentUrl) });
1988
2006
  if (!response.ok) {
1989
2007
  const reason = await this.formatGitHubErrorWithStatus(response);
1990
2008
  throw new Error(`Failed to create review comment on PR ${prUrl}: ${reason}`);
@@ -2040,13 +2058,14 @@ class ApiV3CheerioRestIssueRepository extends BaseGitHubRepository_1.BaseGitHubR
2040
2058
  };
2041
2059
  this.updateBranch = async (prUrl) => {
2042
2060
  const { owner, repo, issueNumber: prNumber } = this.parseIssueUrl(prUrl);
2043
- const response = await this.fetchWithRateLimitRetryForWrite(() => fetch(`https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/pulls/${prNumber}/update-branch`, {
2061
+ const updateBranchUrl = `https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/pulls/${prNumber}/update-branch`;
2062
+ const response = await this.fetchWithRateLimitRetryForWrite(() => fetch(updateBranchUrl, {
2044
2063
  method: 'PUT',
2045
2064
  headers: {
2046
2065
  Authorization: `Bearer ${this.ghToken}`,
2047
2066
  Accept: 'application/vnd.github+json',
2048
2067
  },
2049
- }));
2068
+ }), { method: 'PUT', path: (0, githubRestClient_1.sanitizeRestPath)(updateBranchUrl) });
2050
2069
  if (response.ok) {
2051
2070
  return true;
2052
2071
  }
@@ -2058,12 +2077,13 @@ class ApiV3CheerioRestIssueRepository extends BaseGitHubRepository_1.BaseGitHubR
2058
2077
  };
2059
2078
  this.deletePullRequestBranch = async (prUrl, branchName) => {
2060
2079
  const { owner, repo } = this.parseIssueUrl(prUrl);
2061
- const response = await this.fetchWithRateLimitRetryForWrite(() => fetch(`https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/git/refs/heads/${encodeURIComponent(branchName)}`, {
2080
+ const deleteBranchUrl = `https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/git/refs/heads/${encodeURIComponent(branchName)}`;
2081
+ const response = await this.fetchWithRateLimitRetryForWrite(() => fetch(deleteBranchUrl, {
2062
2082
  method: 'DELETE',
2063
2083
  headers: {
2064
2084
  Authorization: `Bearer ${this.ghToken}`,
2065
2085
  },
2066
- }));
2086
+ }), { method: 'DELETE', path: (0, githubRestClient_1.sanitizeRestPath)(deleteBranchUrl) });
2067
2087
  if (!response.ok && response.status !== 422) {
2068
2088
  const reason = await this.formatGitHubErrorWithStatus(response);
2069
2089
  throw new Error(`Failed to delete branch ${branchName} for PR ${prUrl}: ${reason}`);
@@ -2074,13 +2094,14 @@ class ApiV3CheerioRestIssueRepository extends BaseGitHubRepository_1.BaseGitHubR
2074
2094
  };
2075
2095
  this.fetchIssueBodyResponse = (url) => {
2076
2096
  const { owner, repo, issueNumber } = this.parseIssueUrl(url);
2077
- return this.fetchWithRateLimitRetry(() => fetch(`https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/issues/${issueNumber}`, {
2097
+ const issueBodyUrl = `https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/issues/${issueNumber}`;
2098
+ return this.fetchWithRateLimitRetry(() => fetch(issueBodyUrl, {
2078
2099
  method: 'GET',
2079
2100
  headers: {
2080
2101
  Authorization: `Bearer ${this.ghToken}`,
2081
2102
  Accept: 'application/vnd.github+json',
2082
2103
  },
2083
- }));
2104
+ }), { method: 'GET', path: (0, githubRestClient_1.sanitizeRestPath)(issueBodyUrl) });
2084
2105
  };
2085
2106
  this.parseIssueBodyResponse = async (url, response) => {
2086
2107
  const body = await response.json();
@@ -2114,13 +2135,14 @@ class ApiV3CheerioRestIssueRepository extends BaseGitHubRepository_1.BaseGitHubR
2114
2135
  let page = 1;
2115
2136
  let hasMore = true;
2116
2137
  while (hasMore) {
2117
- const response = await this.fetchWithRateLimitRetry(() => fetch(`https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/issues/${issueNumber}/comments?per_page=${perPage}&page=${page}`, {
2138
+ const commentsUrl = `https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/issues/${issueNumber}/comments?per_page=${perPage}&page=${page}`;
2139
+ const response = await this.fetchWithRateLimitRetry(() => fetch(commentsUrl, {
2118
2140
  method: 'GET',
2119
2141
  headers: {
2120
2142
  Authorization: `Bearer ${this.ghToken}`,
2121
2143
  Accept: 'application/vnd.github+json',
2122
2144
  },
2123
- }));
2145
+ }), { method: 'GET', path: (0, githubRestClient_1.sanitizeRestPath)(commentsUrl) });
2124
2146
  if (!response.ok) {
2125
2147
  await this.throwGitHubError(`Failed to fetch comments for ${url}`, response);
2126
2148
  }
@@ -2150,13 +2172,14 @@ class ApiV3CheerioRestIssueRepository extends BaseGitHubRepository_1.BaseGitHubR
2150
2172
  if (!isPr) {
2151
2173
  return null;
2152
2174
  }
2153
- const detailResponse = await this.fetchWithRateLimitRetry(() => fetch(`https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/pulls/${prNumber}`, {
2175
+ const prDetailUrl = `https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/pulls/${prNumber}`;
2176
+ const detailResponse = await this.fetchWithRateLimitRetry(() => fetch(prDetailUrl, {
2154
2177
  method: 'GET',
2155
2178
  headers: {
2156
2179
  Authorization: `Bearer ${this.ghToken}`,
2157
2180
  Accept: 'application/vnd.github+json',
2158
2181
  },
2159
- }));
2182
+ }), { method: 'GET', path: (0, githubRestClient_1.sanitizeRestPath)(prDetailUrl) });
2160
2183
  if (!detailResponse.ok) {
2161
2184
  await this.throwGitHubError(`Failed to fetch detail for PR ${prUrl}`, detailResponse);
2162
2185
  }
@@ -2185,13 +2208,14 @@ class ApiV3CheerioRestIssueRepository extends BaseGitHubRepository_1.BaseGitHubR
2185
2208
  let page = 1;
2186
2209
  let hasMore = true;
2187
2210
  while (hasMore) {
2188
- const response = await this.fetchWithRateLimitRetry(() => fetch(`https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/pulls/${prNumber}/files?per_page=${perPage}&page=${page}`, {
2211
+ const prDetailFilesUrl = `https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/pulls/${prNumber}/files?per_page=${perPage}&page=${page}`;
2212
+ const response = await this.fetchWithRateLimitRetry(() => fetch(prDetailFilesUrl, {
2189
2213
  method: 'GET',
2190
2214
  headers: {
2191
2215
  Authorization: `Bearer ${this.ghToken}`,
2192
2216
  Accept: 'application/vnd.github+json',
2193
2217
  },
2194
- }));
2218
+ }), { method: 'GET', path: (0, githubRestClient_1.sanitizeRestPath)(prDetailFilesUrl) });
2195
2219
  if (!response.ok) {
2196
2220
  await this.throwGitHubError(`Failed to fetch files for PR ${prUrl}`, response);
2197
2221
  }
@@ -2230,13 +2254,14 @@ class ApiV3CheerioRestIssueRepository extends BaseGitHubRepository_1.BaseGitHubR
2230
2254
  let page = 1;
2231
2255
  let hasMore = true;
2232
2256
  while (hasMore) {
2233
- const response = await this.fetchWithRateLimitRetry(() => fetch(`https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/pulls/${prNumber}/commits?per_page=${perPage}&page=${page}`, {
2257
+ const prCommitsUrl = `https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/pulls/${prNumber}/commits?per_page=${perPage}&page=${page}`;
2258
+ const response = await this.fetchWithRateLimitRetry(() => fetch(prCommitsUrl, {
2234
2259
  method: 'GET',
2235
2260
  headers: {
2236
2261
  Authorization: `Bearer ${this.ghToken}`,
2237
2262
  Accept: 'application/vnd.github+json',
2238
2263
  },
2239
- }));
2264
+ }), { method: 'GET', path: (0, githubRestClient_1.sanitizeRestPath)(prCommitsUrl) });
2240
2265
  if (!response.ok) {
2241
2266
  await this.throwGitHubError(`Failed to fetch commits for PR ${prUrl}`, response);
2242
2267
  }
@@ -2264,13 +2289,14 @@ class ApiV3CheerioRestIssueRepository extends BaseGitHubRepository_1.BaseGitHubR
2264
2289
  this.getIssueOrPullRequestState = async (url) => {
2265
2290
  const { owner, repo, issueNumber, isPr } = this.parseIssueUrl(url);
2266
2291
  if (isPr) {
2267
- const response = await this.fetchWithRateLimitRetry(() => fetch(`https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/pulls/${issueNumber}`, {
2292
+ const prStateUrl = `https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/pulls/${issueNumber}`;
2293
+ const response = await this.fetchWithRateLimitRetry(() => fetch(prStateUrl, {
2268
2294
  method: 'GET',
2269
2295
  headers: {
2270
2296
  Authorization: `Bearer ${this.ghToken}`,
2271
2297
  Accept: 'application/vnd.github+json',
2272
2298
  },
2273
- }));
2299
+ }), { method: 'GET', path: (0, githubRestClient_1.sanitizeRestPath)(prStateUrl) });
2274
2300
  if (!response.ok) {
2275
2301
  await this.throwGitHubError(`Failed to fetch state for ${url}`, response);
2276
2302
  }
@@ -2285,13 +2311,14 @@ class ApiV3CheerioRestIssueRepository extends BaseGitHubRepository_1.BaseGitHubR
2285
2311
  title: body.title,
2286
2312
  };
2287
2313
  }
2288
- const response = await this.fetchWithRateLimitRetry(() => fetch(`https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/issues/${issueNumber}`, {
2314
+ const issueStateUrl = `https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/issues/${issueNumber}`;
2315
+ const response = await this.fetchWithRateLimitRetry(() => fetch(issueStateUrl, {
2289
2316
  method: 'GET',
2290
2317
  headers: {
2291
2318
  Authorization: `Bearer ${this.ghToken}`,
2292
2319
  Accept: 'application/vnd.github+json',
2293
2320
  },
2294
- }));
2321
+ }), { method: 'GET', path: (0, githubRestClient_1.sanitizeRestPath)(issueStateUrl) });
2295
2322
  if (!response.ok) {
2296
2323
  await this.throwGitHubError(`Failed to fetch state for ${url}`, response);
2297
2324
  }
@@ -2311,13 +2338,14 @@ class ApiV3CheerioRestIssueRepository extends BaseGitHubRepository_1.BaseGitHubR
2311
2338
  if (!isPr) {
2312
2339
  return null;
2313
2340
  }
2314
- const response = await this.fetchWithRateLimitRetry(() => fetch(`https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/pulls/${prNumber}`, {
2341
+ const prSummaryUrl = `https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/pulls/${prNumber}`;
2342
+ const response = await this.fetchWithRateLimitRetry(() => fetch(prSummaryUrl, {
2315
2343
  method: 'GET',
2316
2344
  headers: {
2317
2345
  Authorization: `Bearer ${this.ghToken}`,
2318
2346
  Accept: 'application/vnd.github+json',
2319
2347
  },
2320
- }));
2348
+ }), { method: 'GET', path: (0, githubRestClient_1.sanitizeRestPath)(prSummaryUrl) });
2321
2349
  if (!response.ok) {
2322
2350
  const reason = await this.formatGitHubErrorWithStatus(response);
2323
2351
  throw new Error(`Failed to fetch summary for PR ${prUrl}: ${reason}`);
@@ -2338,13 +2366,14 @@ class ApiV3CheerioRestIssueRepository extends BaseGitHubRepository_1.BaseGitHubR
2338
2366
  const { owner, repo, issueNumber } = this.parseIssueUrl(issueOrPrUrl);
2339
2367
  const perPage = 100;
2340
2368
  while (true) {
2341
- const response = await this.fetchWithRateLimitRetry(() => fetch(`https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/issues/${issueNumber}/comments?per_page=${perPage}&page=1`, {
2369
+ const listCommentsUrl = `https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/issues/${issueNumber}/comments?per_page=${perPage}&page=1`;
2370
+ const response = await this.fetchWithRateLimitRetry(() => fetch(listCommentsUrl, {
2342
2371
  method: 'GET',
2343
2372
  headers: {
2344
2373
  Authorization: `Bearer ${this.ghToken}`,
2345
2374
  Accept: 'application/vnd.github+json',
2346
2375
  },
2347
- }));
2376
+ }), { method: 'GET', path: (0, githubRestClient_1.sanitizeRestPath)(listCommentsUrl) });
2348
2377
  if (!response.ok) {
2349
2378
  await this.throwGitHubError(`Failed to fetch comments for ${issueOrPrUrl}`, response);
2350
2379
  }
@@ -2353,13 +2382,14 @@ class ApiV3CheerioRestIssueRepository extends BaseGitHubRepository_1.BaseGitHubR
2353
2382
  throw new Error(`Unexpected response shape when fetching comments for ${issueOrPrUrl}`);
2354
2383
  }
2355
2384
  for (const comment of body) {
2356
- const deleteResponse = await this.fetchWithRateLimitRetryForWrite(() => fetch(`https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/issues/comments/${comment.id}`, {
2385
+ const deleteCommentUrl = `https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/issues/comments/${comment.id}`;
2386
+ const deleteResponse = await this.fetchWithRateLimitRetryForWrite(() => fetch(deleteCommentUrl, {
2357
2387
  method: 'DELETE',
2358
2388
  headers: {
2359
2389
  Authorization: `Bearer ${this.ghToken}`,
2360
2390
  Accept: 'application/vnd.github+json',
2361
2391
  },
2362
- }));
2392
+ }), { method: 'DELETE', path: (0, githubRestClient_1.sanitizeRestPath)(deleteCommentUrl) });
2363
2393
  if (!deleteResponse.ok) {
2364
2394
  await this.throwGitHubError(`Failed to delete comment ${comment.id} on ${issueOrPrUrl}`, deleteResponse);
2365
2395
  }