github-issue-tower-defence-management 2.154.9 → 2.155.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 (147) hide show
  1. package/README.md +45 -1
  2. package/bin/adapter/entry-points/cli/fleetConfig.js +33 -1
  3. package/bin/adapter/entry-points/cli/fleetConfig.js.map +1 -1
  4. package/bin/adapter/entry-points/cli/index.js +159 -51
  5. package/bin/adapter/entry-points/cli/index.js.map +1 -1
  6. package/bin/adapter/entry-points/cli/notifyFinishedIssuePreparationRetry.js +27 -0
  7. package/bin/adapter/entry-points/cli/notifyFinishedIssuePreparationRetry.js.map +1 -0
  8. package/bin/adapter/entry-points/cli/projectConfig.js +5 -0
  9. package/bin/adapter/entry-points/cli/projectConfig.js.map +1 -1
  10. package/bin/adapter/entry-points/cli/resumableSessionSelect.js +45 -0
  11. package/bin/adapter/entry-points/cli/resumableSessionSelect.js.map +1 -0
  12. package/bin/adapter/entry-points/console/ui-dist/assets/index-B_lVAA3P.css +2 -0
  13. package/bin/adapter/entry-points/console/ui-dist/assets/index-CN6FXgfc.js +93 -0
  14. package/bin/adapter/entry-points/console/ui-dist/index.html +2 -2
  15. package/bin/adapter/entry-points/console/webServer.js +20 -0
  16. package/bin/adapter/entry-points/console/webServer.js.map +1 -1
  17. package/bin/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.js +30 -6
  18. package/bin/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.js.map +1 -1
  19. package/bin/adapter/proxy/TokenListLoader.js +3 -2
  20. package/bin/adapter/proxy/TokenListLoader.js.map +1 -1
  21. package/bin/adapter/repositories/FileSystemResumableSessionTranscriptRepository.js +188 -0
  22. package/bin/adapter/repositories/FileSystemResumableSessionTranscriptRepository.js.map +1 -0
  23. package/bin/adapter/repositories/FileSystemWorkerSessionFailureStreakRepository.js +98 -0
  24. package/bin/adapter/repositories/FileSystemWorkerSessionFailureStreakRepository.js.map +1 -0
  25. package/bin/adapter/repositories/FileSystemWorkerSessionLogRepository.js +57 -0
  26. package/bin/adapter/repositories/FileSystemWorkerSessionLogRepository.js.map +1 -0
  27. package/bin/adapter/repositories/GitHubIssueCommentRepository.js +15 -2
  28. package/bin/adapter/repositories/GitHubIssueCommentRepository.js.map +1 -1
  29. package/bin/adapter/repositories/LocalStorageUrgentStoryLaunchHoldRepository.js +245 -0
  30. package/bin/adapter/repositories/LocalStorageUrgentStoryLaunchHoldRepository.js.map +1 -0
  31. package/bin/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.js +110 -19
  32. package/bin/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.js.map +1 -1
  33. package/bin/adapter/repositories/issue/RestIssueRepository.js +1 -0
  34. package/bin/adapter/repositories/issue/RestIssueRepository.js.map +1 -1
  35. package/bin/domain/entities/WorkerRequestText.js +6 -0
  36. package/bin/domain/entities/WorkerRequestText.js.map +1 -0
  37. package/bin/domain/entities/WorkerSessionFailureStreak.js +3 -0
  38. package/bin/domain/entities/WorkerSessionFailureStreak.js.map +1 -0
  39. package/bin/domain/services/commentCreateWithDedupRetry.js +46 -0
  40. package/bin/domain/services/commentCreateWithDedupRetry.js.map +1 -0
  41. package/bin/domain/usecases/AssignNoAssigneeIssueToManagerUseCase.js +2 -1
  42. package/bin/domain/usecases/AssignNoAssigneeIssueToManagerUseCase.js.map +1 -1
  43. package/bin/domain/usecases/CloseIssueAsRequestApplier.js +29 -0
  44. package/bin/domain/usecases/CloseIssueAsRequestApplier.js.map +1 -0
  45. package/bin/domain/usecases/HandleScheduledEventUseCase.js +1 -0
  46. package/bin/domain/usecases/HandleScheduledEventUseCase.js.map +1 -1
  47. package/bin/domain/usecases/NotifyFinishedIssuePreparationUseCase.js +61 -15
  48. package/bin/domain/usecases/NotifyFinishedIssuePreparationUseCase.js.map +1 -1
  49. package/bin/domain/usecases/ResumableSessionSelectUseCase.js +153 -0
  50. package/bin/domain/usecases/ResumableSessionSelectUseCase.js.map +1 -0
  51. package/bin/domain/usecases/RevertNotReadyReviewQueueIssueUseCase.js +12 -7
  52. package/bin/domain/usecases/RevertNotReadyReviewQueueIssueUseCase.js.map +1 -1
  53. package/bin/domain/usecases/RevertOrphanedPreparationUseCase.js +59 -21
  54. package/bin/domain/usecases/RevertOrphanedPreparationUseCase.js.map +1 -1
  55. package/bin/domain/usecases/StartPreparationUseCase.js +157 -3
  56. package/bin/domain/usecases/StartPreparationUseCase.js.map +1 -1
  57. package/bin/domain/usecases/WorkerSessionEndClassifyUseCase.js +225 -0
  58. package/bin/domain/usecases/WorkerSessionEndClassifyUseCase.js.map +1 -0
  59. package/bin/domain/usecases/adapter-interfaces/ResumableSessionTranscriptRepository.js +3 -0
  60. package/bin/domain/usecases/adapter-interfaces/ResumableSessionTranscriptRepository.js.map +1 -0
  61. package/bin/domain/usecases/adapter-interfaces/UrgentStoryLaunchHoldRepository.js +3 -0
  62. package/bin/domain/usecases/adapter-interfaces/UrgentStoryLaunchHoldRepository.js.map +1 -0
  63. package/bin/domain/usecases/adapter-interfaces/WorkerSessionFailureStreakRepository.js +3 -0
  64. package/bin/domain/usecases/adapter-interfaces/WorkerSessionFailureStreakRepository.js.map +1 -0
  65. package/bin/domain/usecases/adapter-interfaces/WorkerSessionLogRepository.js +3 -0
  66. package/bin/domain/usecases/adapter-interfaces/WorkerSessionLogRepository.js.map +1 -0
  67. package/bin/domain/usecases/extractCloseIssueAs.js +24 -0
  68. package/bin/domain/usecases/extractCloseIssueAs.js.map +1 -0
  69. package/bin/domain/usecases/findLastAgentReport.js +9 -3
  70. package/bin/domain/usecases/findLastAgentReport.js.map +1 -1
  71. package/bin/domain/usecases/isTransientApiError.js +3 -1
  72. package/bin/domain/usecases/isTransientApiError.js.map +1 -1
  73. package/bin/domain/usecases/urgentStoryLaunchHoldDecide.js +76 -0
  74. package/bin/domain/usecases/urgentStoryLaunchHoldDecide.js.map +1 -0
  75. package/package.json +1 -1
  76. package/types/adapter/entry-points/cli/fleetConfig.d.ts +3 -0
  77. package/types/adapter/entry-points/cli/fleetConfig.d.ts.map +1 -1
  78. package/types/adapter/entry-points/cli/index.d.ts.map +1 -1
  79. package/types/adapter/entry-points/cli/notifyFinishedIssuePreparationRetry.d.ts +8 -0
  80. package/types/adapter/entry-points/cli/notifyFinishedIssuePreparationRetry.d.ts.map +1 -0
  81. package/types/adapter/entry-points/cli/projectConfig.d.ts +3 -0
  82. package/types/adapter/entry-points/cli/projectConfig.d.ts.map +1 -1
  83. package/types/adapter/entry-points/cli/resumableSessionSelect.d.ts +12 -0
  84. package/types/adapter/entry-points/cli/resumableSessionSelect.d.ts.map +1 -0
  85. package/types/adapter/entry-points/console/webServer.d.ts +1 -0
  86. package/types/adapter/entry-points/console/webServer.d.ts.map +1 -1
  87. package/types/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.d.ts +3 -0
  88. package/types/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.d.ts.map +1 -1
  89. package/types/adapter/proxy/TokenListLoader.d.ts +1 -0
  90. package/types/adapter/proxy/TokenListLoader.d.ts.map +1 -1
  91. package/types/adapter/repositories/FileSystemResumableSessionTranscriptRepository.d.ts +13 -0
  92. package/types/adapter/repositories/FileSystemResumableSessionTranscriptRepository.d.ts.map +1 -0
  93. package/types/adapter/repositories/FileSystemWorkerSessionFailureStreakRepository.d.ts +12 -0
  94. package/types/adapter/repositories/FileSystemWorkerSessionFailureStreakRepository.d.ts.map +1 -0
  95. package/types/adapter/repositories/FileSystemWorkerSessionLogRepository.d.ts +5 -0
  96. package/types/adapter/repositories/FileSystemWorkerSessionLogRepository.d.ts.map +1 -0
  97. package/types/adapter/repositories/GitHubIssueCommentRepository.d.ts +5 -0
  98. package/types/adapter/repositories/GitHubIssueCommentRepository.d.ts.map +1 -1
  99. package/types/adapter/repositories/LocalStorageUrgentStoryLaunchHoldRepository.d.ts +30 -0
  100. package/types/adapter/repositories/LocalStorageUrgentStoryLaunchHoldRepository.d.ts.map +1 -0
  101. package/types/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.d.ts +3 -1
  102. package/types/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.d.ts.map +1 -1
  103. package/types/adapter/repositories/issue/RestIssueRepository.d.ts.map +1 -1
  104. package/types/domain/entities/Comment.d.ts +1 -0
  105. package/types/domain/entities/Comment.d.ts.map +1 -1
  106. package/types/domain/entities/SearchedIssue.d.ts +1 -0
  107. package/types/domain/entities/SearchedIssue.d.ts.map +1 -1
  108. package/types/domain/entities/WorkerRequestText.d.ts +2 -0
  109. package/types/domain/entities/WorkerRequestText.d.ts.map +1 -0
  110. package/types/domain/entities/WorkerSessionFailureStreak.d.ts +5 -0
  111. package/types/domain/entities/WorkerSessionFailureStreak.d.ts.map +1 -0
  112. package/types/domain/services/commentCreateWithDedupRetry.d.ts +11 -0
  113. package/types/domain/services/commentCreateWithDedupRetry.d.ts.map +1 -0
  114. package/types/domain/usecases/AssignNoAssigneeIssueToManagerUseCase.d.ts.map +1 -1
  115. package/types/domain/usecases/CloseIssueAsRequestApplier.d.ts +16 -0
  116. package/types/domain/usecases/CloseIssueAsRequestApplier.d.ts.map +1 -0
  117. package/types/domain/usecases/HandleScheduledEventUseCase.d.ts +1 -0
  118. package/types/domain/usecases/HandleScheduledEventUseCase.d.ts.map +1 -1
  119. package/types/domain/usecases/NotifyFinishedIssuePreparationUseCase.d.ts +4 -1
  120. package/types/domain/usecases/NotifyFinishedIssuePreparationUseCase.d.ts.map +1 -1
  121. package/types/domain/usecases/ResumableSessionSelectUseCase.d.ts +35 -0
  122. package/types/domain/usecases/ResumableSessionSelectUseCase.d.ts.map +1 -0
  123. package/types/domain/usecases/RevertNotReadyReviewQueueIssueUseCase.d.ts +3 -1
  124. package/types/domain/usecases/RevertNotReadyReviewQueueIssueUseCase.d.ts.map +1 -1
  125. package/types/domain/usecases/RevertOrphanedPreparationUseCase.d.ts +5 -2
  126. package/types/domain/usecases/RevertOrphanedPreparationUseCase.d.ts.map +1 -1
  127. package/types/domain/usecases/StartPreparationUseCase.d.ts +12 -1
  128. package/types/domain/usecases/StartPreparationUseCase.d.ts.map +1 -1
  129. package/types/domain/usecases/WorkerSessionEndClassifyUseCase.d.ts +27 -0
  130. package/types/domain/usecases/WorkerSessionEndClassifyUseCase.d.ts.map +1 -0
  131. package/types/domain/usecases/adapter-interfaces/ResumableSessionTranscriptRepository.d.ts +22 -0
  132. package/types/domain/usecases/adapter-interfaces/ResumableSessionTranscriptRepository.d.ts.map +1 -0
  133. package/types/domain/usecases/adapter-interfaces/UrgentStoryLaunchHoldRepository.d.ts +11 -0
  134. package/types/domain/usecases/adapter-interfaces/UrgentStoryLaunchHoldRepository.d.ts.map +1 -0
  135. package/types/domain/usecases/adapter-interfaces/WorkerSessionFailureStreakRepository.d.ts +7 -0
  136. package/types/domain/usecases/adapter-interfaces/WorkerSessionFailureStreakRepository.d.ts.map +1 -0
  137. package/types/domain/usecases/adapter-interfaces/WorkerSessionLogRepository.d.ts +11 -0
  138. package/types/domain/usecases/adapter-interfaces/WorkerSessionLogRepository.d.ts.map +1 -0
  139. package/types/domain/usecases/extractCloseIssueAs.d.ts +12 -0
  140. package/types/domain/usecases/extractCloseIssueAs.d.ts.map +1 -0
  141. package/types/domain/usecases/findLastAgentReport.d.ts +1 -0
  142. package/types/domain/usecases/findLastAgentReport.d.ts.map +1 -1
  143. package/types/domain/usecases/isTransientApiError.d.ts.map +1 -1
  144. package/types/domain/usecases/urgentStoryLaunchHoldDecide.d.ts +48 -0
  145. package/types/domain/usecases/urgentStoryLaunchHoldDecide.d.ts.map +1 -0
  146. package/bin/adapter/entry-points/console/ui-dist/assets/index-BCjejjC0.css +0 -2
  147. package/bin/adapter/entry-points/console/ui-dist/assets/index-CcgrBS5P.js +0 -93
@@ -9,6 +9,7 @@ const BaseGitHubRepository_1 = require("../BaseGitHubRepository");
9
9
  const githubGraphqlClient_1 = require("../githubGraphqlClient");
10
10
  const utils_1 = require("../utils");
11
11
  const localStorageCacheDirectory_1 = require("../localStorageCacheDirectory");
12
+ const isTransientApiError_1 = require("../../../domain/usecases/isTransientApiError");
12
13
  const githubRateLimitRetry_1 = require("./githubRateLimitRetry");
13
14
  const githubRestClient_1 = require("../githubRestClient");
14
15
  const githubSecondaryRateLimitBreaker_1 = require("./githubSecondaryRateLimitBreaker");
@@ -79,6 +80,7 @@ const RELATED_OPEN_PULL_REQUEST_URLS_BATCH_SIZE = 100;
79
80
  const RELATED_OPEN_PULL_REQUEST_URLS_TIMELINE_PAGE_SIZE = 100;
80
81
  const ISSUE_EVENTS_PAGE_SIZE = 100;
81
82
  const ISSUE_EVENT_COUNT_AT_WHICH_GITHUB_STOPS_LISTING_ISSUE_EVENTS = 310;
83
+ const REOPENED_TIMELINE_ITEM_COUNT_AT_WHICH_GITHUB_STOPS_LISTING_REOPENED_TIMELINE_ITEMS = 310;
82
84
  function isIssueTimelineResponse(value) {
83
85
  if (typeof value !== 'object' || value === null)
84
86
  return false;
@@ -163,9 +165,22 @@ function isIssueLatestReopenedTimelineItemResponse(value) {
163
165
  if (!isRecord(timelineItems))
164
166
  return false;
165
167
  const nodes = timelineItems.nodes;
166
- return (Array.isArray(nodes) &&
168
+ return (typeof timelineItems.totalCount === 'number' &&
169
+ Array.isArray(nodes) &&
167
170
  nodes.every((node) => isRecord(node) && typeof node.createdAt === 'string'));
168
171
  }
172
+ function isRepositoryIssueEventFeedResponseItem(value) {
173
+ if (!isRecord(value))
174
+ return false;
175
+ const issue = value.issue;
176
+ return (typeof value.event === 'string' &&
177
+ typeof value.created_at === 'string' &&
178
+ isRecord(issue) &&
179
+ typeof issue.number === 'number');
180
+ }
181
+ function isRepositoryIssueEventFeedResponse(value) {
182
+ return (Array.isArray(value) && value.every(isRepositoryIssueEventFeedResponseItem));
183
+ }
169
184
  function listGraphqlResponseErrors(value) {
170
185
  if (!isRecord(value))
171
186
  return [];
@@ -537,7 +552,7 @@ class ApiV3CheerioRestIssueRepository extends BaseGitHubRepository_1.BaseGitHubR
537
552
  project = freshProject;
538
553
  }
539
554
  catch (error) {
540
- if (!isFullFetch && cache !== null) {
555
+ if (cache !== null && (!isFullFetch || (0, isTransientApiError_1.isTransientApiError)(error))) {
541
556
  console.warn(`Failed to refresh project metadata, using cached. projectId: ${projectId}, error: ${String(error)}`);
542
557
  project = cache.project;
543
558
  }
@@ -557,6 +572,7 @@ class ApiV3CheerioRestIssueRepository extends BaseGitHubRepository_1.BaseGitHubR
557
572
  storyOptionsChanged ||
558
573
  cacheHasIssuesMissingStoryOptionIdSchema;
559
574
  if (effectiveIsFullFetch) {
575
+ const projectMetadataFellBackToCache = cache !== null && project === cache.project;
560
576
  const itemIdsKnownBeforeFetch = new Set(cache?.issues.map((issue) => issue.itemId) ?? []);
561
577
  const items = await this.graphqlProjectItemRepository.fetchProjectItems(projectId);
562
578
  const cachedIssuesKnownBeforeFetch = cache?.issues ?? [];
@@ -584,7 +600,11 @@ class ApiV3CheerioRestIssueRepository extends BaseGitHubRepository_1.BaseGitHubR
584
600
  return { mergedIssues: reconciledIssues, mergedProject };
585
601
  });
586
602
  this.lastIssuesFetchedAtByProjectId.set(projectId, nowIso);
587
- return { issues, project: mergedProject, cacheUsed: false };
603
+ return {
604
+ issues,
605
+ project: mergedProject,
606
+ cacheUsed: projectMetadataFellBackToCache,
607
+ };
588
608
  }
589
609
  const itemIdsKnownBeforeFetch = new Set(cache.issues.map((issue) => issue.itemId));
590
610
  const lastFetchedAt = new Date(cache.lastFetchedAt);
@@ -1699,21 +1719,33 @@ class ApiV3CheerioRestIssueRepository extends BaseGitHubRepository_1.BaseGitHubR
1699
1719
  const resolved = new Map();
1700
1720
  for (let start = 0; start < references.length; start += RELATED_OPEN_PULL_REQUEST_URLS_BATCH_SIZE) {
1701
1721
  const batchReferences = references.slice(start, start + RELATED_OPEN_PULL_REQUEST_URLS_BATCH_SIZE);
1702
- let prUrlsByIssueUrl;
1703
- try {
1704
- prUrlsByIssueUrl =
1705
- await this.fetchRelatedOpenPrUrlsInOneQuery(batchReferences);
1706
- }
1707
- catch (error) {
1708
- console.warn(`ApiV3CheerioRestIssueRepository: batched related open pull request query failed, leaving ${batchReferences.length} issue(s) to per-issue resolution. error: ${error instanceof Error ? error.message : String(error)}`);
1709
- continue;
1710
- }
1722
+ const prUrlsByIssueUrl = await this.resolveRelatedOpenPrUrlsByBisectingFailedBatches(batchReferences);
1711
1723
  for (const [issueUrl, prUrls] of prUrlsByIssueUrl) {
1712
1724
  resolved.set(issueUrl, prUrls);
1713
1725
  }
1714
1726
  }
1715
1727
  return resolved;
1716
1728
  };
1729
+ this.resolveRelatedOpenPrUrlsByBisectingFailedBatches = async (references) => {
1730
+ try {
1731
+ return await this.fetchRelatedOpenPrUrlsInOneQuery(references);
1732
+ }
1733
+ catch (error) {
1734
+ const errorMessage = error instanceof Error ? error.message : String(error);
1735
+ if (references.length === 1) {
1736
+ console.warn(`ApiV3CheerioRestIssueRepository: related open pull request query failed for issue ${references[0].issueUrl}, leaving it unresolved. error: ${errorMessage}`);
1737
+ return new Map();
1738
+ }
1739
+ const splitPoint = Math.ceil(references.length / 2);
1740
+ const firstHalfResolved = await this.resolveRelatedOpenPrUrlsByBisectingFailedBatches(references.slice(0, splitPoint));
1741
+ const secondHalfResolved = await this.resolveRelatedOpenPrUrlsByBisectingFailedBatches(references.slice(splitPoint));
1742
+ const merged = new Map(firstHalfResolved);
1743
+ for (const [issueUrl, prUrls] of secondHalfResolved) {
1744
+ merged.set(issueUrl, prUrls);
1745
+ }
1746
+ return merged;
1747
+ }
1748
+ };
1717
1749
  this.fetchRelatedOpenPrUrlsInOneQuery = async (references) => {
1718
1750
  const aliasOf = (index) => `issue${index}`;
1719
1751
  const variableDeclarations = references
@@ -2099,7 +2131,12 @@ class ApiV3CheerioRestIssueRepository extends BaseGitHubRepository_1.BaseGitHubR
2099
2131
  if (issueEventListingScan.listingOutcome === 'listedEveryEvent') {
2100
2132
  return issueEventListingScan.latestReopenedEventAt;
2101
2133
  }
2102
- return this.fetchLatestReopenedTimelineItemCreatedAt(issue);
2134
+ const latestReopenedTimelineItemListing = await this.fetchLatestReopenedTimelineItemListing(issue);
2135
+ if (latestReopenedTimelineItemListing.listingOutcome ===
2136
+ 'listedEveryReopenedTimelineItem') {
2137
+ return latestReopenedTimelineItemListing.latestReopenedTimelineItemCreatedAt;
2138
+ }
2139
+ return this.scanRepositoryIssueEventFeedForLatestReopenedEventAt(issue, latestReopenedTimelineItemListing.lastListedReopenedTimelineItemCreatedAt);
2103
2140
  };
2104
2141
  this.scanIssueEventListingForLatestReopenedEventAt = async (issue) => {
2105
2142
  const { owner, repo, issueNumber } = this.parseIssueUrl(issue.url);
@@ -2146,13 +2183,14 @@ class ApiV3CheerioRestIssueRepository extends BaseGitHubRepository_1.BaseGitHubR
2146
2183
  }
2147
2184
  return { listingOutcome: 'listedEveryEvent', latestReopenedEventAt };
2148
2185
  };
2149
- this.fetchLatestReopenedTimelineItemCreatedAt = async (issue) => {
2186
+ this.fetchLatestReopenedTimelineItemListing = async (issue) => {
2150
2187
  const { owner, repo, issueNumber } = this.parseIssueUrl(issue.url);
2151
2188
  const query = `
2152
2189
  query IssueLatestReopenedTimelineItem($owner: String!, $repo: String!, $issueNumber: Int!) {
2153
2190
  repository(owner: $owner, name: $repo) {
2154
2191
  issue(number: $issueNumber) {
2155
2192
  timelineItems(last: 1, itemTypes: [REOPENED_EVENT]) {
2193
+ totalCount
2156
2194
  nodes {
2157
2195
  ... on ReopenedEvent {
2158
2196
  createdAt
@@ -2179,12 +2217,65 @@ class ApiV3CheerioRestIssueRepository extends BaseGitHubRepository_1.BaseGitHubR
2179
2217
  throw new Error(`GitHub GraphQL API returned errors for the latest reopened timeline item of issue ${issue.url}: ${JSON.stringify(graphqlErrors)}`);
2180
2218
  }
2181
2219
  if (!isIssueLatestReopenedTimelineItemResponse(responseBody)) {
2182
- throw new Error(`GitHub GraphQL API returned no timeline items for issue ${issue.url}`);
2220
+ throw new Error(`GitHub GraphQL API returned no reopened timeline items with a totalCount for issue ${issue.url}`);
2221
+ }
2222
+ const { totalCount: reopenedTimelineItemTotalCount, nodes: reopenedTimelineItems, } = responseBody.data.repository.issue.timelineItems;
2223
+ const lastListedReopenedTimelineItemCreatedAt = reopenedTimelineItems.length === 0
2224
+ ? null
2225
+ : new Date(reopenedTimelineItems[reopenedTimelineItems.length - 1].createdAt);
2226
+ if (reopenedTimelineItemTotalCount <
2227
+ REOPENED_TIMELINE_ITEM_COUNT_AT_WHICH_GITHUB_STOPS_LISTING_REOPENED_TIMELINE_ITEMS) {
2228
+ return {
2229
+ listingOutcome: 'listedEveryReopenedTimelineItem',
2230
+ latestReopenedTimelineItemCreatedAt: lastListedReopenedTimelineItemCreatedAt,
2231
+ };
2183
2232
  }
2184
- const reopenedTimelineItems = responseBody.data.repository.issue.timelineItems.nodes;
2185
- if (reopenedTimelineItems.length === 0)
2186
- return null;
2187
- return new Date(reopenedTimelineItems[reopenedTimelineItems.length - 1].createdAt);
2233
+ if (lastListedReopenedTimelineItemCreatedAt === null) {
2234
+ throw new Error(`GitHub GraphQL API reported ${reopenedTimelineItemTotalCount} reopened timeline items but listed none for issue ${issue.url}`);
2235
+ }
2236
+ return {
2237
+ listingOutcome: 'stoppedAtGitHubReopenedTimelineItemListingLimit',
2238
+ lastListedReopenedTimelineItemCreatedAt,
2239
+ };
2240
+ };
2241
+ this.scanRepositoryIssueEventFeedForLatestReopenedEventAt = async (issue, lastListedReopenedTimelineItemCreatedAt) => {
2242
+ const { owner, repo, issueNumber } = this.parseIssueUrl(issue.url);
2243
+ const ownerSegment = encodeURIComponent(owner);
2244
+ const repoSegment = encodeURIComponent(repo);
2245
+ let page = 1;
2246
+ let hasNextPage = true;
2247
+ while (hasNextPage) {
2248
+ const repositoryIssueEventFeedUrl = `https://api.github.com/repos/${ownerSegment}/${repoSegment}/issues/events?per_page=${ISSUE_EVENTS_PAGE_SIZE}&page=${page}`;
2249
+ const response = await this.fetchWithRateLimitRetry(() => fetch(repositoryIssueEventFeedUrl, {
2250
+ method: 'GET',
2251
+ headers: {
2252
+ Authorization: `Bearer ${this.ghToken}`,
2253
+ Accept: 'application/vnd.github+json',
2254
+ },
2255
+ }), { method: 'GET', path: (0, githubRestClient_1.sanitizeRestPath)(repositoryIssueEventFeedUrl) });
2256
+ if (!response.ok) {
2257
+ const reason = await this.formatGitHubErrorWithStatus(response);
2258
+ throw new Error(`Failed to fetch page ${page} of the repository issue event feed of ${owner}/${repo} for issue ${issue.url}: ${reason}`);
2259
+ }
2260
+ const body = await response.json();
2261
+ if (!isRepositoryIssueEventFeedResponse(body)) {
2262
+ throw new Error(`Unexpected response shape when fetching page ${page} of the repository issue event feed of ${owner}/${repo} for issue ${issue.url}`);
2263
+ }
2264
+ for (const feedEvent of body) {
2265
+ const feedEventCreatedAt = new Date(feedEvent.created_at);
2266
+ if (feedEventCreatedAt < lastListedReopenedTimelineItemCreatedAt) {
2267
+ return lastListedReopenedTimelineItemCreatedAt;
2268
+ }
2269
+ if (feedEvent.event === 'reopened' &&
2270
+ feedEvent.issue.number === issueNumber) {
2271
+ return feedEventCreatedAt;
2272
+ }
2273
+ }
2274
+ const linkHeader = response.headers.get('Link') ?? '';
2275
+ hasNextPage = linkHeader.includes('rel="next"');
2276
+ page++;
2277
+ }
2278
+ throw new Error(`The repository issue event feed of ${owner}/${repo} ended before listing a reopened event of issue ${issue.url} or an event earlier than its last listed reopened timeline item created at ${lastListedReopenedTimelineItemCreatedAt.toISOString()}`);
2188
2279
  };
2189
2280
  this.getPullRequestChangedFilePaths = async (prUrl) => {
2190
2281
  const { owner, repo, issueNumber: prNumber } = this.parseIssueUrl(prUrl);