github-issue-tower-defence-management 2.19.1 → 2.19.3

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 (795) hide show
  1. package/bin/adapter/entry-points/cli/index.js +1 -3
  2. package/bin/adapter/entry-points/cli/index.js.map +1 -1
  3. package/bin/adapter/entry-points/console/consoleOperationApi.js +6 -12
  4. package/bin/adapter/entry-points/console/consoleOperationApi.js.map +1 -1
  5. package/bin/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.js +1 -1
  6. package/bin/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.js.map +1 -1
  7. package/bin/adapter/repositories/issue/RestIssueRepository.js +9 -2
  8. package/bin/adapter/repositories/issue/RestIssueRepository.js.map +1 -1
  9. package/package.json +5 -1
  10. package/types/adapter/entry-points/cli/index.d.ts.map +1 -1
  11. package/types/adapter/entry-points/console/consoleOperationApi.d.ts.map +1 -1
  12. package/types/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.d.ts +1 -1
  13. package/types/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.d.ts.map +1 -1
  14. package/types/adapter/repositories/issue/RestIssueRepository.d.ts +5 -1
  15. package/types/adapter/repositories/issue/RestIssueRepository.d.ts.map +1 -1
  16. package/types/domain/usecases/adapter-interfaces/IssueRepository.d.ts +1 -1
  17. package/types/domain/usecases/adapter-interfaces/IssueRepository.d.ts.map +1 -1
  18. package/.env.example +0 -7
  19. package/.gitattributes +0 -2
  20. package/.github/CODEOWNERS +0 -6
  21. package/.github/copilot-instructions.md +0 -6
  22. package/.github/dependabot.yml +0 -19
  23. package/.github/instructions/code-review.instructions.md +0 -50
  24. package/.github/workflows/api-created_issue_pr.yml +0 -97
  25. package/.github/workflows/ci-failure-issue-creator.yml +0 -48
  26. package/.github/workflows/close-manual-prs.yml +0 -80
  27. package/.github/workflows/codeql.yml +0 -107
  28. package/.github/workflows/commit-lint.yml +0 -85
  29. package/.github/workflows/configs/commitlint.config.cjs +0 -32
  30. package/.github/workflows/configs/commitlint.config.js +0 -32
  31. package/.github/workflows/console-ui.yml +0 -68
  32. package/.github/workflows/create-pr.yml +0 -116
  33. package/.github/workflows/empty-format-test-job.yml +0 -34
  34. package/.github/workflows/format.yml +0 -28
  35. package/.github/workflows/publish.yml +0 -111
  36. package/.github/workflows/secret-scan.yml +0 -51
  37. package/.github/workflows/test.yml +0 -58
  38. package/.github/workflows/umino-project.yml +0 -207
  39. package/.gitleaks.toml +0 -22
  40. package/.prettierignore +0 -31
  41. package/.prettierrc +0 -5
  42. package/CHANGELOG.md +0 -4162
  43. package/CONTRIBUTING.md +0 -60
  44. package/bin/adapter/entry-points/handlers/resolveUnansweredOwnerCalls.js +0 -12
  45. package/bin/adapter/entry-points/handlers/resolveUnansweredOwnerCalls.js.map +0 -1
  46. package/bin/adapter/repositories/FileSystemSilentSessionNotifiedStateRepository.js +0 -125
  47. package/bin/adapter/repositories/FileSystemSilentSessionNotifiedStateRepository.js.map +0 -1
  48. package/bin/adapter/repositories/issue/issueTimelineUtils.js +0 -38
  49. package/bin/adapter/repositories/issue/issueTimelineUtils.js.map +0 -1
  50. package/bin/domain/entities/UnansweredOwnerCall.js +0 -3
  51. package/bin/domain/entities/UnansweredOwnerCall.js.map +0 -1
  52. package/bin/domain/entities/WorkingTime.js +0 -3
  53. package/bin/domain/entities/WorkingTime.js.map +0 -1
  54. package/bin/domain/usecases/ChangeStatusLongInReviewIssueUseCase.js +0 -37
  55. package/bin/domain/usecases/ChangeStatusLongInReviewIssueUseCase.js.map +0 -1
  56. package/bin/domain/usecases/ConvertCheckboxToIssueInStoryIssueUseCase.js +0 -118
  57. package/bin/domain/usecases/ConvertCheckboxToIssueInStoryIssueUseCase.js.map +0 -1
  58. package/bin/domain/usecases/GenerateWorkingTimeReportUseCase.js +0 -180
  59. package/bin/domain/usecases/GenerateWorkingTimeReportUseCase.js.map +0 -1
  60. package/bin/domain/usecases/TriagerApprovalDispatchUseCase.js +0 -160
  61. package/bin/domain/usecases/TriagerApprovalDispatchUseCase.js.map +0 -1
  62. package/bin/domain/usecases/adapter-interfaces/SilentSessionNotifiedStateRepository.js +0 -3
  63. package/bin/domain/usecases/adapter-interfaces/SilentSessionNotifiedStateRepository.js.map +0 -1
  64. package/bin/domain/usecases/awaitingOwnerApprovalMessage.js +0 -7
  65. package/bin/domain/usecases/awaitingOwnerApprovalMessage.js.map +0 -1
  66. package/bin/domain/usecases/findLastAgentDeclaringReport.js +0 -12
  67. package/bin/domain/usecases/findLastAgentDeclaringReport.js.map +0 -1
  68. package/bin/domain/usecases/isPullRequestDeclaredUnnecessary.js +0 -25
  69. package/bin/domain/usecases/isPullRequestDeclaredUnnecessary.js.map +0 -1
  70. package/bin/domain/usecases/isWaitingForOwnerApproval.js +0 -16
  71. package/bin/domain/usecases/isWaitingForOwnerApproval.js.map +0 -1
  72. package/bin/domain/usecases/returnedToAwaitingWorkspaceMessage.js +0 -7
  73. package/bin/domain/usecases/returnedToAwaitingWorkspaceMessage.js.map +0 -1
  74. package/commitlint.config.js +0 -6
  75. package/eslint.config.mjs +0 -76
  76. package/jest.config.js +0 -84
  77. package/jest.setup.js +0 -1
  78. package/lib/buffer-equal-constant-time-compat/index.js +0 -17
  79. package/lib/buffer-equal-constant-time-compat/index.test.js +0 -54
  80. package/lib/buffer-equal-constant-time-compat/package.json +0 -5
  81. package/lib/typescript-compat/bin/tsc +0 -2
  82. package/lib/typescript-compat/bin/tsserver +0 -2
  83. package/lib/typescript-compat/lib/typescript.js +0 -1
  84. package/lib/typescript-compat/package.json +0 -13
  85. package/renovate.json +0 -46
  86. package/scripts/copyConsoleUiDist.mjs +0 -35
  87. package/scripts/defaultBranchTipVerify.sh +0 -23
  88. package/scripts/testWorkflowRunVerify.sh +0 -47
  89. package/scripts/verifyConsoleUiDist.mjs +0 -96
  90. package/src/adapter/ci/consoleUiWorkflowTimeout.test.ts +0 -61
  91. package/src/adapter/ci/publishTestWorkflowGate.test.ts +0 -653
  92. package/src/adapter/ci/workflowRunCancellation.test.ts +0 -72
  93. package/src/adapter/entry-points/cli/fleetConfig.test.ts +0 -553
  94. package/src/adapter/entry-points/cli/fleetConfig.ts +0 -266
  95. package/src/adapter/entry-points/cli/index.test.ts +0 -2837
  96. package/src/adapter/entry-points/cli/index.ts +0 -1488
  97. package/src/adapter/entry-points/cli/projectConfig.test.ts +0 -342
  98. package/src/adapter/entry-points/cli/projectConfig.ts +0 -500
  99. package/src/adapter/entry-points/console/consoleAirplaneSnapshotApi.test.ts +0 -658
  100. package/src/adapter/entry-points/console/consoleAirplaneSnapshotApi.ts +0 -482
  101. package/src/adapter/entry-points/console/consoleDataDelivery.test.ts +0 -222
  102. package/src/adapter/entry-points/console/consoleDataDelivery.ts +0 -134
  103. package/src/adapter/entry-points/console/consoleDoneStore.test.ts +0 -169
  104. package/src/adapter/entry-points/console/consoleDoneStore.ts +0 -139
  105. package/src/adapter/entry-points/console/consoleGithubTokenResolver.test.ts +0 -275
  106. package/src/adapter/entry-points/console/consoleGithubTokenResolver.ts +0 -113
  107. package/src/adapter/entry-points/console/consoleImageProxy.test.ts +0 -140
  108. package/src/adapter/entry-points/console/consoleImageProxy.ts +0 -111
  109. package/src/adapter/entry-points/console/consoleItemUrlLookup.test.ts +0 -63
  110. package/src/adapter/entry-points/console/consoleItemUrlLookup.ts +0 -66
  111. package/src/adapter/entry-points/console/consoleOperationApi.test.ts +0 -2523
  112. package/src/adapter/entry-points/console/consoleOperationApi.ts +0 -936
  113. package/src/adapter/entry-points/console/consoleProjectResolver.test.ts +0 -246
  114. package/src/adapter/entry-points/console/consoleProjectResolver.ts +0 -111
  115. package/src/adapter/entry-points/console/consoleReadApi.test.ts +0 -714
  116. package/src/adapter/entry-points/console/consoleReadApi.ts +0 -361
  117. package/src/adapter/entry-points/console/consoleTabNames.test.ts +0 -52
  118. package/src/adapter/entry-points/console/consoleTabNames.ts +0 -37
  119. package/src/adapter/entry-points/console/consoleUiDistSync.test.ts +0 -66
  120. package/src/adapter/entry-points/console/dashboardComposeService.test.ts +0 -595
  121. package/src/adapter/entry-points/console/dashboardComposeService.ts +0 -256
  122. package/src/adapter/entry-points/console/dashboardEmitterComposerKey.test.ts +0 -180
  123. package/src/adapter/entry-points/console/ensureConsoleRunning.test.ts +0 -115
  124. package/src/adapter/entry-points/console/ensureConsoleRunning.ts +0 -64
  125. package/src/adapter/entry-points/console/ui/.storybook/main.ts +0 -12
  126. package/src/adapter/entry-points/console/ui/.storybook/preview.ts +0 -15
  127. package/src/adapter/entry-points/console/ui/biome.json +0 -53
  128. package/src/adapter/entry-points/console/ui/components.json +0 -20
  129. package/src/adapter/entry-points/console/ui/e2e/consoleScenario.spec.ts +0 -725
  130. package/src/adapter/entry-points/console/ui/e2e/consoleTestHarness.ts +0 -831
  131. package/src/adapter/entry-points/console/ui/e2e/playwright.config.ts +0 -30
  132. package/src/adapter/entry-points/console/ui/e2e/tsconfig.json +0 -18
  133. package/src/adapter/entry-points/console/ui/index.html +0 -12
  134. package/src/adapter/entry-points/console/ui/jest-globals.cjs +0 -26
  135. package/src/adapter/entry-points/console/ui/jest.setup.ts +0 -1
  136. package/src/adapter/entry-points/console/ui/jest.styleMock.js +0 -1
  137. package/src/adapter/entry-points/console/ui/public/sw.js +0 -60
  138. package/src/adapter/entry-points/console/ui/src/components/ui/badge.stories.tsx +0 -35
  139. package/src/adapter/entry-points/console/ui/src/components/ui/badge.tsx +0 -28
  140. package/src/adapter/entry-points/console/ui/src/components/ui/button.stories.tsx +0 -34
  141. package/src/adapter/entry-points/console/ui/src/components/ui/button.tsx +0 -50
  142. package/src/adapter/entry-points/console/ui/src/features/console/components/content/ConsoleCopyCodeButton.stories.tsx +0 -28
  143. package/src/adapter/entry-points/console/ui/src/features/console/components/content/ConsoleCopyCodeButton.test.tsx +0 -65
  144. package/src/adapter/entry-points/console/ui/src/features/console/components/content/ConsoleCopyCodeButton.tsx +0 -19
  145. package/src/adapter/entry-points/console/ui/src/features/console/components/content/ConsoleMarkdownContent.stories.tsx +0 -61
  146. package/src/adapter/entry-points/console/ui/src/features/console/components/content/ConsoleMarkdownContent.test.tsx +0 -199
  147. package/src/adapter/entry-points/console/ui/src/features/console/components/content/ConsoleMarkdownContent.tsx +0 -166
  148. package/src/adapter/entry-points/console/ui/src/features/console/components/content/ConsoleMermaidDiagram.stories.tsx +0 -22
  149. package/src/adapter/entry-points/console/ui/src/features/console/components/content/ConsoleMermaidDiagram.test.tsx +0 -74
  150. package/src/adapter/entry-points/console/ui/src/features/console/components/content/ConsoleMermaidDiagram.tsx +0 -84
  151. package/src/adapter/entry-points/console/ui/src/features/console/components/content/ConsoleMermaidDiagramModalScreen.stories.tsx +0 -27
  152. package/src/adapter/entry-points/console/ui/src/features/console/components/content/ConsoleMermaidDiagramModalScreen.test.tsx +0 -85
  153. package/src/adapter/entry-points/console/ui/src/features/console/components/content/ConsoleMermaidDiagramModalScreen.tsx +0 -97
  154. package/src/adapter/entry-points/console/ui/src/features/console/components/content/ConsoleReferenceLink.stories.tsx +0 -76
  155. package/src/adapter/entry-points/console/ui/src/features/console/components/content/ConsoleReferenceLink.test.tsx +0 -56
  156. package/src/adapter/entry-points/console/ui/src/features/console/components/content/ConsoleReferenceLink.tsx +0 -39
  157. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleChangedFileList.stories.tsx +0 -32
  158. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleChangedFileList.test.tsx +0 -105
  159. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleChangedFileList.tsx +0 -157
  160. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleCommentComposer.stories.tsx +0 -68
  161. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleCommentComposer.test.tsx +0 -612
  162. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleCommentComposer.tsx +0 -276
  163. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleCommentList.stories.tsx +0 -33
  164. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleCommentList.test.tsx +0 -57
  165. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleCommentList.tsx +0 -76
  166. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleCommitList.stories.tsx +0 -25
  167. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleCommitList.test.tsx +0 -55
  168. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleCommitList.tsx +0 -49
  169. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleCopyUrlButton.stories.tsx +0 -24
  170. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleCopyUrlButton.test.tsx +0 -63
  171. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleCopyUrlButton.tsx +0 -19
  172. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleFetchFailureAlert.stories.tsx +0 -33
  173. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleFetchFailureAlert.test.tsx +0 -62
  174. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleFetchFailureAlert.tsx +0 -59
  175. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleFileDiff.stories.tsx +0 -38
  176. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleFileDiff.test.tsx +0 -151
  177. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleFileDiff.tsx +0 -245
  178. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleItemDetail.stories.tsx +0 -514
  179. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleItemDetail.test.tsx +0 -727
  180. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleItemDetail.tsx +0 -387
  181. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleItemIcon.stories.tsx +0 -82
  182. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleItemIcon.test.tsx +0 -52
  183. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleItemIcon.tsx +0 -32
  184. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsolePullRequestDetail.stories.tsx +0 -48
  185. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsolePullRequestDetail.test.tsx +0 -235
  186. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsolePullRequestDetail.tsx +0 -145
  187. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsolePullRequestMergeableChip.stories.tsx +0 -29
  188. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsolePullRequestMergeableChip.test.tsx +0 -30
  189. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsolePullRequestMergeableChip.tsx +0 -50
  190. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsolePullRequestStatusBadges.stories.tsx +0 -38
  191. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsolePullRequestStatusBadges.test.tsx +0 -54
  192. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsolePullRequestStatusBadges.tsx +0 -60
  193. package/src/adapter/entry-points/console/ui/src/features/console/components/layout/ConsoleAirplaneModeButton.stories.tsx +0 -56
  194. package/src/adapter/entry-points/console/ui/src/features/console/components/layout/ConsoleAirplaneModeButton.test.tsx +0 -111
  195. package/src/adapter/entry-points/console/ui/src/features/console/components/layout/ConsoleAirplaneModeButton.tsx +0 -92
  196. package/src/adapter/entry-points/console/ui/src/features/console/components/layout/ConsolePanel.stories.tsx +0 -51
  197. package/src/adapter/entry-points/console/ui/src/features/console/components/layout/ConsolePanel.test.tsx +0 -59
  198. package/src/adapter/entry-points/console/ui/src/features/console/components/layout/ConsolePanel.tsx +0 -39
  199. package/src/adapter/entry-points/console/ui/src/features/console/components/layout/ConsoleTabList.stories.tsx +0 -207
  200. package/src/adapter/entry-points/console/ui/src/features/console/components/layout/ConsoleTabList.test.tsx +0 -332
  201. package/src/adapter/entry-points/console/ui/src/features/console/components/layout/ConsoleTabList.tsx +0 -211
  202. package/src/adapter/entry-points/console/ui/src/features/console/components/layout/ConsoleTimerSettingsModalDialog.stories.tsx +0 -49
  203. package/src/adapter/entry-points/console/ui/src/features/console/components/layout/ConsoleTimerSettingsModalDialog.test.tsx +0 -157
  204. package/src/adapter/entry-points/console/ui/src/features/console/components/layout/ConsoleTimerSettingsModalDialog.tsx +0 -118
  205. package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleCopyStoryNameButton.stories.tsx +0 -23
  206. package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleCopyStoryNameButton.test.tsx +0 -40
  207. package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleCopyStoryNameButton.tsx +0 -17
  208. package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleItemList.stories.tsx +0 -101
  209. package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleItemList.test.tsx +0 -102
  210. package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleItemList.tsx +0 -75
  211. package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleItemSummary.stories.tsx +0 -40
  212. package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleItemSummary.test.tsx +0 -213
  213. package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleItemSummary.tsx +0 -115
  214. package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleQueuedItemRow.stories.tsx +0 -50
  215. package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleQueuedItemRow.test.tsx +0 -98
  216. package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleQueuedItemRow.tsx +0 -66
  217. package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleQueuedList.stories.tsx +0 -69
  218. package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleQueuedList.test.tsx +0 -101
  219. package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleQueuedList.tsx +0 -75
  220. package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleStoryList.stories.tsx +0 -137
  221. package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleStoryList.test.tsx +0 -554
  222. package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleStoryList.tsx +0 -400
  223. package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleStoryReorderPanel.stories.tsx +0 -30
  224. package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleStoryReorderPanel.test.tsx +0 -111
  225. package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleStoryReorderPanel.tsx +0 -91
  226. package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleStorySummary.stories.tsx +0 -27
  227. package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleStorySummary.test.tsx +0 -24
  228. package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleStorySummary.tsx +0 -28
  229. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleCloseActions.stories.tsx +0 -14
  230. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleCloseActions.test.tsx +0 -21
  231. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleCloseActions.tsx +0 -26
  232. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleDangerousActions.stories.tsx +0 -14
  233. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleDangerousActions.test.tsx +0 -40
  234. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleDangerousActions.tsx +0 -37
  235. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleErrorToast.stories.tsx +0 -33
  236. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleNextActionDateActions.stories.tsx +0 -20
  237. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleNextActionDateActions.test.tsx +0 -48
  238. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleNextActionDateActions.tsx +0 -42
  239. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleOfflinePendingActionsPanel.stories.tsx +0 -115
  240. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleOfflinePendingActionsPanel.test.tsx +0 -262
  241. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleOfflinePendingActionsPanel.tsx +0 -123
  242. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleOperationMenu.stories.tsx +0 -65
  243. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleOperationMenu.test.tsx +0 -159
  244. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleOperationMenu.tsx +0 -55
  245. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsolePullRequestReviewActions.stories.tsx +0 -20
  246. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsolePullRequestReviewActions.test.tsx +0 -93
  247. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsolePullRequestReviewActions.tsx +0 -45
  248. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleStatusActions.stories.tsx +0 -17
  249. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleStatusActions.test.tsx +0 -49
  250. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleStatusActions.tsx +0 -66
  251. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleUndoToast.stories.tsx +0 -47
  252. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleUndoToast.test.tsx +0 -95
  253. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleUndoToast.tsx +0 -62
  254. package/src/adapter/entry-points/console/ui/src/features/console/components/shared/ConsoleClipboardCopyButton.stories.tsx +0 -32
  255. package/src/adapter/entry-points/console/ui/src/features/console/components/shared/ConsoleClipboardCopyButton.test.tsx +0 -101
  256. package/src/adapter/entry-points/console/ui/src/features/console/components/shared/ConsoleClipboardCopyButton.tsx +0 -80
  257. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useAirplaneMode.test.ts +0 -304
  258. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useAirplaneMode.ts +0 -167
  259. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleActionQueue.test.ts +0 -498
  260. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleActionQueue.ts +0 -277
  261. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleCaches.test.ts +0 -99
  262. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleCaches.ts +0 -90
  263. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleDetailPrefetch.test.ts +0 -110
  264. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleDetailPrefetch.ts +0 -83
  265. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleFeaturesConfig.test.ts +0 -51
  266. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleFeaturesConfig.ts +0 -39
  267. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleItemDetailData.test.ts +0 -229
  268. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleItemDetailData.ts +0 -194
  269. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleNavigation.test.ts +0 -151
  270. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleNavigation.ts +0 -128
  271. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleOperations.test.ts +0 -629
  272. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleOperations.ts +0 -417
  273. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleOverlay.test.ts +0 -40
  274. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleOverlay.ts +0 -71
  275. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsolePjcode.test.ts +0 -24
  276. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsolePjcode.ts +0 -17
  277. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleProjectList.test.ts +0 -97
  278. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleProjectList.ts +0 -44
  279. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleProjectTimer.test.ts +0 -59
  280. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleProjectTimer.ts +0 -26
  281. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleReferenceLink.test.ts +0 -48
  282. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleReferenceLink.ts +0 -39
  283. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleResource.test.ts +0 -41
  284. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleResource.ts +0 -57
  285. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleSwipeNavigation.test.ts +0 -115
  286. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleSwipeNavigation.ts +0 -115
  287. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleTabData.test.ts +0 -546
  288. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleTabData.ts +0 -278
  289. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleTimerSettings.test.ts +0 -110
  290. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleTimerSettings.ts +0 -70
  291. package/src/adapter/entry-points/console/ui/src/features/console/lib/airplaneSnapshot.test.ts +0 -387
  292. package/src/adapter/entry-points/console/ui/src/features/console/lib/airplaneSnapshot.ts +0 -407
  293. package/src/adapter/entry-points/console/ui/src/features/console/lib/consoleApi.test.ts +0 -446
  294. package/src/adapter/entry-points/console/ui/src/features/console/lib/consoleApi.ts +0 -533
  295. package/src/adapter/entry-points/console/ui/src/features/console/lib/imageProxy.test.ts +0 -78
  296. package/src/adapter/entry-points/console/ui/src/features/console/lib/imageProxy.ts +0 -34
  297. package/src/adapter/entry-points/console/ui/src/features/console/lib/markdown.test.ts +0 -252
  298. package/src/adapter/entry-points/console/ui/src/features/console/lib/markdown.ts +0 -207
  299. package/src/adapter/entry-points/console/ui/src/features/console/lib/mermaidLoader.test.ts +0 -27
  300. package/src/adapter/entry-points/console/ui/src/features/console/lib/mermaidLoader.ts +0 -71
  301. package/src/adapter/entry-points/console/ui/src/features/console/lib/mermaidZoom.test.ts +0 -88
  302. package/src/adapter/entry-points/console/ui/src/features/console/lib/mermaidZoom.ts +0 -34
  303. package/src/adapter/entry-points/console/ui/src/features/console/lib/navigation.ts +0 -7
  304. package/src/adapter/entry-points/console/ui/src/features/console/lib/resourceCache.test.ts +0 -104
  305. package/src/adapter/entry-points/console/ui/src/features/console/lib/resourceCache.ts +0 -79
  306. package/src/adapter/entry-points/console/ui/src/features/console/logic/actionToast.test.ts +0 -200
  307. package/src/adapter/entry-points/console/ui/src/features/console/logic/actionToast.ts +0 -122
  308. package/src/adapter/entry-points/console/ui/src/features/console/logic/clipboard.test.ts +0 -90
  309. package/src/adapter/entry-points/console/ui/src/features/console/logic/clipboard.ts +0 -37
  310. package/src/adapter/entry-points/console/ui/src/features/console/logic/colors.test.ts +0 -34
  311. package/src/adapter/entry-points/console/ui/src/features/console/logic/colors.ts +0 -85
  312. package/src/adapter/entry-points/console/ui/src/features/console/logic/diff.test.ts +0 -81
  313. package/src/adapter/entry-points/console/ui/src/features/console/logic/diff.ts +0 -61
  314. package/src/adapter/entry-points/console/ui/src/features/console/logic/fileStatus.test.ts +0 -35
  315. package/src/adapter/entry-points/console/ui/src/features/console/logic/fileStatus.ts +0 -21
  316. package/src/adapter/entry-points/console/ui/src/features/console/logic/fileTree.test.ts +0 -96
  317. package/src/adapter/entry-points/console/ui/src/features/console/logic/fileTree.ts +0 -93
  318. package/src/adapter/entry-points/console/ui/src/features/console/logic/grouping.test.ts +0 -229
  319. package/src/adapter/entry-points/console/ui/src/features/console/logic/grouping.ts +0 -106
  320. package/src/adapter/entry-points/console/ui/src/features/console/logic/imageFile.test.ts +0 -18
  321. package/src/adapter/entry-points/console/ui/src/features/console/logic/imageFile.ts +0 -16
  322. package/src/adapter/entry-points/console/ui/src/features/console/logic/itemIcons.test.ts +0 -97
  323. package/src/adapter/entry-points/console/ui/src/features/console/logic/itemIcons.ts +0 -95
  324. package/src/adapter/entry-points/console/ui/src/features/console/logic/navigation.test.ts +0 -53
  325. package/src/adapter/entry-points/console/ui/src/features/console/logic/navigation.ts +0 -33
  326. package/src/adapter/entry-points/console/ui/src/features/console/logic/operations.test.ts +0 -72
  327. package/src/adapter/entry-points/console/ui/src/features/console/logic/operations.ts +0 -61
  328. package/src/adapter/entry-points/console/ui/src/features/console/logic/overlay.test.ts +0 -325
  329. package/src/adapter/entry-points/console/ui/src/features/console/logic/overlay.ts +0 -94
  330. package/src/adapter/entry-points/console/ui/src/features/console/logic/postedComments.test.ts +0 -44
  331. package/src/adapter/entry-points/console/ui/src/features/console/logic/postedComments.ts +0 -17
  332. package/src/adapter/entry-points/console/ui/src/features/console/logic/references.test.ts +0 -103
  333. package/src/adapter/entry-points/console/ui/src/features/console/logic/references.ts +0 -64
  334. package/src/adapter/entry-points/console/ui/src/features/console/logic/relativeTime.test.ts +0 -74
  335. package/src/adapter/entry-points/console/ui/src/features/console/logic/relativeTime.ts +0 -53
  336. package/src/adapter/entry-points/console/ui/src/features/console/logic/swipe.test.ts +0 -34
  337. package/src/adapter/entry-points/console/ui/src/features/console/logic/swipe.ts +0 -28
  338. package/src/adapter/entry-points/console/ui/src/features/console/logic/tabAdvance.test.ts +0 -89
  339. package/src/adapter/entry-points/console/ui/src/features/console/logic/tabAdvance.ts +0 -30
  340. package/src/adapter/entry-points/console/ui/src/features/console/logic/timerSettings.test.ts +0 -61
  341. package/src/adapter/entry-points/console/ui/src/features/console/logic/timerSettings.ts +0 -77
  342. package/src/adapter/entry-points/console/ui/src/features/console/logic/types.ts +0 -173
  343. package/src/adapter/entry-points/console/ui/src/features/console/pages/ConsoleItemDetailContainer.test.tsx +0 -629
  344. package/src/adapter/entry-points/console/ui/src/features/console/pages/ConsoleItemDetailContainer.tsx +0 -351
  345. package/src/adapter/entry-points/console/ui/src/features/console/pages/ConsolePage.test.tsx +0 -1469
  346. package/src/adapter/entry-points/console/ui/src/features/console/pages/ConsolePage.tsx +0 -736
  347. package/src/adapter/entry-points/console/ui/src/features/console/pages/ConsoleReferenceLinkContainer.tsx +0 -25
  348. package/src/adapter/entry-points/console/ui/src/features/console/testing/fixtures.ts +0 -418
  349. package/src/adapter/entry-points/console/ui/src/index.css +0 -1592
  350. package/src/adapter/entry-points/console/ui/src/lib/utils.ts +0 -4
  351. package/src/adapter/entry-points/console/ui/src/main.tsx +0 -21
  352. package/src/adapter/entry-points/console/ui/src/vite-env.d.ts +0 -1
  353. package/src/adapter/entry-points/console/ui/tsconfig.json +0 -26
  354. package/src/adapter/entry-points/console/ui/vite.config.ts +0 -24
  355. package/src/adapter/entry-points/console/ui-dist/assets/index-BYDbK1Fj.js +0 -88
  356. package/src/adapter/entry-points/console/ui-dist/assets/index-KyQISRuQ.css +0 -2
  357. package/src/adapter/entry-points/console/ui-dist/index.html +0 -13
  358. package/src/adapter/entry-points/console/ui-dist/sw.js +0 -60
  359. package/src/adapter/entry-points/console/webServer.test.ts +0 -2945
  360. package/src/adapter/entry-points/console/webServer.ts +0 -940
  361. package/src/adapter/entry-points/function/getStoryObjectMap.ts +0 -3
  362. package/src/adapter/entry-points/handlers/FileSystemConsoleTabsRepository.test.ts +0 -218
  363. package/src/adapter/entry-points/handlers/FileSystemConsoleTabsRepository.ts +0 -113
  364. package/src/adapter/entry-points/handlers/GetStoryObjectMapUseCaseHandler.test.ts +0 -110
  365. package/src/adapter/entry-points/handlers/GetStoryObjectMapUseCaseHandler.ts +0 -116
  366. package/src/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.test.ts +0 -859
  367. package/src/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.ts +0 -984
  368. package/src/adapter/entry-points/handlers/InTmuxByHumanSessionTokenCountHandler.test.ts +0 -126
  369. package/src/adapter/entry-points/handlers/InTmuxByHumanSessionTokenCountHandler.ts +0 -80
  370. package/src/adapter/entry-points/handlers/LiveSessionOauthTokenSelectHandler.test.ts +0 -491
  371. package/src/adapter/entry-points/handlers/LiveSessionOauthTokenSelectHandler.ts +0 -137
  372. package/src/adapter/entry-points/handlers/OauthTokenSelectHandler.test.ts +0 -508
  373. package/src/adapter/entry-points/handlers/OauthTokenSelectHandler.ts +0 -156
  374. package/src/adapter/entry-points/handlers/consoleListsWriter.test.ts +0 -332
  375. package/src/adapter/entry-points/handlers/consoleListsWriter.ts +0 -60
  376. package/src/adapter/entry-points/handlers/dashboardRowWriter.test.ts +0 -112
  377. package/src/adapter/entry-points/handlers/dashboardRowWriter.ts +0 -51
  378. package/src/adapter/entry-points/handlers/inTmuxByHumanDataWriter.test.ts +0 -285
  379. package/src/adapter/entry-points/handlers/inTmuxByHumanDataWriter.ts +0 -109
  380. package/src/adapter/entry-points/handlers/inTmuxByHumanSessionReconciler.test.ts +0 -170
  381. package/src/adapter/entry-points/handlers/inTmuxByHumanSessionReconciler.ts +0 -40
  382. package/src/adapter/entry-points/handlers/machineStatusWriter.test.ts +0 -191
  383. package/src/adapter/entry-points/handlers/machineStatusWriter.ts +0 -110
  384. package/src/adapter/entry-points/handlers/notifySilentTmuxSessions.test.ts +0 -548
  385. package/src/adapter/entry-points/handlers/notifySilentTmuxSessions.ts +0 -192
  386. package/src/adapter/entry-points/handlers/ownerCallFileCleaner.test.ts +0 -111
  387. package/src/adapter/entry-points/handlers/ownerCallFileCleaner.ts +0 -25
  388. package/src/adapter/entry-points/handlers/ownerCallFileStore.test.ts +0 -286
  389. package/src/adapter/entry-points/handlers/ownerCallFileStore.ts +0 -145
  390. package/src/adapter/entry-points/handlers/ownerReplyMarkerDirectoryResolve.test.ts +0 -55
  391. package/src/adapter/entry-points/handlers/ownerReplyMarkerDirectoryResolve.ts +0 -36
  392. package/src/adapter/entry-points/handlers/resetDegeneratedTmuxSessions.test.ts +0 -72
  393. package/src/adapter/entry-points/handlers/resetDegeneratedTmuxSessions.ts +0 -74
  394. package/src/adapter/entry-points/handlers/rotationOrderFileWriter.test.ts +0 -181
  395. package/src/adapter/entry-points/handlers/rotationOrderFileWriter.ts +0 -18
  396. package/src/adapter/entry-points/handlers/situationFileWriter.test.ts +0 -489
  397. package/src/adapter/entry-points/handlers/situationFileWriter.ts +0 -176
  398. package/src/adapter/entry-points/handlers/staleTmuxSessionCleaner.test.ts +0 -169
  399. package/src/adapter/entry-points/handlers/staleTmuxSessionCleaner.ts +0 -32
  400. package/src/adapter/entry-points/handlers/subscriptionDisabledTokenHandler.ts +0 -41
  401. package/src/adapter/entry-points/handlers/tokenExhaustionHandover.ts +0 -81
  402. package/src/adapter/entry-points/handlers/tokenStatusWriter.test.ts +0 -266
  403. package/src/adapter/entry-points/handlers/tokenStatusWriter.ts +0 -262
  404. package/src/adapter/proxy/ClaudeMessageResponseParser.test.ts +0 -211
  405. package/src/adapter/proxy/ClaudeMessageResponseParser.ts +0 -181
  406. package/src/adapter/proxy/RateLimitCache.test.ts +0 -1077
  407. package/src/adapter/proxy/RateLimitCache.ts +0 -385
  408. package/src/adapter/proxy/TokenListLoader.test.ts +0 -183
  409. package/src/adapter/proxy/TokenListLoader.ts +0 -75
  410. package/src/adapter/proxy/ensureProxyRunning.test.ts +0 -85
  411. package/src/adapter/proxy/ensureProxyRunning.ts +0 -41
  412. package/src/adapter/proxy/proxyEntry.test.ts +0 -552
  413. package/src/adapter/proxy/proxyEntry.ts +0 -214
  414. package/src/adapter/repositories/BaseGitHubRepository.test.ts +0 -26
  415. package/src/adapter/repositories/BaseGitHubRepository.ts +0 -26
  416. package/src/adapter/repositories/BrowserGitHubProjectRepository.test.ts +0 -108
  417. package/src/adapter/repositories/BrowserGitHubProjectRepository.ts +0 -118
  418. package/src/adapter/repositories/CliGitHubGraphqlRateLimitRepository.test.ts +0 -71
  419. package/src/adapter/repositories/CliGitHubGraphqlRateLimitRepository.ts +0 -21
  420. package/src/adapter/repositories/ConfigurableSilentSessionMessageComposer.test.ts +0 -324
  421. package/src/adapter/repositories/ConfigurableSilentSessionMessageComposer.ts +0 -148
  422. package/src/adapter/repositories/FetchWebhookRepository.ts +0 -8
  423. package/src/adapter/repositories/FileHandoverStateRepository.test.ts +0 -114
  424. package/src/adapter/repositories/FileHandoverStateRepository.ts +0 -73
  425. package/src/adapter/repositories/FileSystemInteractiveLiveSessionTranscriptResolver.test.ts +0 -514
  426. package/src/adapter/repositories/FileSystemInteractiveLiveSessionTranscriptResolver.ts +0 -109
  427. package/src/adapter/repositories/FileSystemKevReportWatermarkRepository.test.ts +0 -284
  428. package/src/adapter/repositories/FileSystemKevReportWatermarkRepository.ts +0 -128
  429. package/src/adapter/repositories/FileSystemSessionAssistantTurnsRepository.test.ts +0 -138
  430. package/src/adapter/repositories/FileSystemSessionAssistantTurnsRepository.ts +0 -113
  431. package/src/adapter/repositories/FileSystemSessionDegenerationCooldownStateRepository.test.ts +0 -121
  432. package/src/adapter/repositories/FileSystemSessionDegenerationCooldownStateRepository.ts +0 -103
  433. package/src/adapter/repositories/FileSystemSessionOutputActivityRepository.test.ts +0 -360
  434. package/src/adapter/repositories/FileSystemSessionOutputActivityRepository.ts +0 -77
  435. package/src/adapter/repositories/FileSystemSessionRecordReader.test.ts +0 -59
  436. package/src/adapter/repositories/FileSystemSessionRecordReader.ts +0 -31
  437. package/src/adapter/repositories/FileSystemSilentSessionCandidateStateRepository.test.ts +0 -287
  438. package/src/adapter/repositories/FileSystemSilentSessionCandidateStateRepository.ts +0 -125
  439. package/src/adapter/repositories/FileSystemSilentSessionHubTaskStatusCacheRepository.test.ts +0 -285
  440. package/src/adapter/repositories/FileSystemSilentSessionHubTaskStatusCacheRepository.ts +0 -119
  441. package/src/adapter/repositories/FileSystemSubAgentLivenessResolver.test.ts +0 -237
  442. package/src/adapter/repositories/FileSystemSubAgentLivenessResolver.ts +0 -57
  443. package/src/adapter/repositories/FileSystemSubAgentSilentSecondsResolver.test.ts +0 -56
  444. package/src/adapter/repositories/FileSystemSubAgentSilentSecondsResolver.ts +0 -32
  445. package/src/adapter/repositories/FileSystemSubAgentTranscriptDirectoryResolver.test.ts +0 -68
  446. package/src/adapter/repositories/FileSystemSubAgentTranscriptDirectoryResolver.ts +0 -23
  447. package/src/adapter/repositories/GitHubIssueCheckpointRepository.test.ts +0 -51
  448. package/src/adapter/repositories/GitHubIssueCheckpointRepository.ts +0 -47
  449. package/src/adapter/repositories/GitHubIssueCommentRepository.test.ts +0 -278
  450. package/src/adapter/repositories/GitHubIssueCommentRepository.ts +0 -109
  451. package/src/adapter/repositories/GoogleSpreadsheetRepository.integration.test.ts +0 -265
  452. package/src/adapter/repositories/GoogleSpreadsheetRepository.test.ts +0 -431
  453. package/src/adapter/repositories/GoogleSpreadsheetRepository.ts +0 -268
  454. package/src/adapter/repositories/GraphqlProjectRepository.diskCache.test.ts +0 -272
  455. package/src/adapter/repositories/GraphqlProjectRepository.errorHandling.test.ts +0 -111
  456. package/src/adapter/repositories/GraphqlProjectRepository.fetchProjectId.test.ts +0 -254
  457. package/src/adapter/repositories/GraphqlProjectRepository.test.ts +0 -209
  458. package/src/adapter/repositories/GraphqlProjectRepository.ts +0 -707
  459. package/src/adapter/repositories/GraphqlProjectRepositoryProjectLocation.test.ts +0 -297
  460. package/src/adapter/repositories/KyHttpRepository.ts +0 -8
  461. package/src/adapter/repositories/KySlackRepository.test.ts +0 -172
  462. package/src/adapter/repositories/KySlackRepository.ts +0 -298
  463. package/src/adapter/repositories/LocalCommandIssueAttachmentRepository.test.ts +0 -193
  464. package/src/adapter/repositories/LocalCommandIssueAttachmentRepository.ts +0 -106
  465. package/src/adapter/repositories/LocalProcessLiveSessionProcessSnapshotProvider.test.ts +0 -190
  466. package/src/adapter/repositories/LocalProcessLiveSessionProcessSnapshotProvider.ts +0 -102
  467. package/src/adapter/repositories/LocalStorageCacheRepository.test.ts +0 -265
  468. package/src/adapter/repositories/LocalStorageCacheRepository.ts +0 -79
  469. package/src/adapter/repositories/LocalStorageRepository.integration.test.ts +0 -142
  470. package/src/adapter/repositories/LocalStorageRepository.test.ts +0 -173
  471. package/src/adapter/repositories/LocalStorageRepository.ts +0 -30
  472. package/src/adapter/repositories/NoUnansweredOwnerCallStatusProvider.test.ts +0 -15
  473. package/src/adapter/repositories/NoUnansweredOwnerCallStatusProvider.ts +0 -9
  474. package/src/adapter/repositories/NodeLocalCommandRunner.largeOutput.test.ts +0 -16
  475. package/src/adapter/repositories/NodeLocalCommandRunner.test.ts +0 -118
  476. package/src/adapter/repositories/NodeLocalCommandRunner.ts +0 -66
  477. package/src/adapter/repositories/NodeProcessSignalRepository.test.ts +0 -82
  478. package/src/adapter/repositories/NodeProcessSignalRepository.ts +0 -47
  479. package/src/adapter/repositories/NodeSubAgentProcessLister.test.ts +0 -44
  480. package/src/adapter/repositories/NodeSubAgentProcessLister.ts +0 -40
  481. package/src/adapter/repositories/NodeTmuxSessionRepository.test.ts +0 -546
  482. package/src/adapter/repositories/NodeTmuxSessionRepository.ts +0 -245
  483. package/src/adapter/repositories/OauthAPIClaudeRepository.ts +0 -279
  484. package/src/adapter/repositories/OauthAPIProxyClaudeRepository.test.ts +0 -166
  485. package/src/adapter/repositories/OauthAPIProxyClaudeRepository.ts +0 -32
  486. package/src/adapter/repositories/OauthProxyClaudeRepository.test.ts +0 -428
  487. package/src/adapter/repositories/OauthProxyClaudeRepository.ts +0 -124
  488. package/src/adapter/repositories/ProcClaudeHandoverSessionRepository.test.ts +0 -242
  489. package/src/adapter/repositories/ProcClaudeHandoverSessionRepository.ts +0 -261
  490. package/src/adapter/repositories/ProcClaudeInteractiveSessionRepository.test.ts +0 -184
  491. package/src/adapter/repositories/ProcClaudeInteractiveSessionRepository.ts +0 -138
  492. package/src/adapter/repositories/ProcClaudeLiveSessionRepository.test.ts +0 -208
  493. package/src/adapter/repositories/ProcClaudeLiveSessionRepository.ts +0 -130
  494. package/src/adapter/repositories/ProcFsProcessEnvironReader.test.ts +0 -60
  495. package/src/adapter/repositories/ProcFsProcessEnvironReader.ts +0 -38
  496. package/src/adapter/repositories/ProcHostMetricsRepository.test.ts +0 -207
  497. package/src/adapter/repositories/ProcHostMetricsRepository.ts +0 -181
  498. package/src/adapter/repositories/ProcTakeOwnershipSpawnRepository.test.ts +0 -231
  499. package/src/adapter/repositories/ProcTakeOwnershipSpawnRepository.ts +0 -134
  500. package/src/adapter/repositories/ProcessListSessionSubAgentActivityRepository.test.ts +0 -121
  501. package/src/adapter/repositories/ProcessListSessionSubAgentActivityRepository.ts +0 -58
  502. package/src/adapter/repositories/ProjectIssuesCacheRepository.test.ts +0 -218
  503. package/src/adapter/repositories/ProjectIssuesCacheRepository.ts +0 -141
  504. package/src/adapter/repositories/ProxyClaudeTokenUsageRepository.test.ts +0 -1195
  505. package/src/adapter/repositories/ProxyClaudeTokenUsageRepository.ts +0 -185
  506. package/src/adapter/repositories/ProxyRateLimitCacheRepository.test.ts +0 -116
  507. package/src/adapter/repositories/ProxyRateLimitCacheRepository.ts +0 -67
  508. package/src/adapter/repositories/RateLimitSnapshotRepository.ts +0 -50
  509. package/src/adapter/repositories/RealSleeper.test.ts +0 -19
  510. package/src/adapter/repositories/RealSleeper.ts +0 -12
  511. package/src/adapter/repositories/RestProjectRepository.test.ts +0 -277
  512. package/src/adapter/repositories/RestProjectRepository.ts +0 -129
  513. package/src/adapter/repositories/SqliteClaudeMessageResponseRepository.test.ts +0 -313
  514. package/src/adapter/repositories/SqliteClaudeMessageResponseRepository.ts +0 -164
  515. package/src/adapter/repositories/SystemDateRepository.test.ts +0 -60
  516. package/src/adapter/repositories/SystemDateRepository.ts +0 -34
  517. package/src/adapter/repositories/TmuxSilentSessionNotificationRepository.test.ts +0 -253
  518. package/src/adapter/repositories/TmuxSilentSessionNotificationRepository.ts +0 -158
  519. package/src/adapter/repositories/TranscriptOwnerCallStatusProvider.queuedReply.test.ts +0 -171
  520. package/src/adapter/repositories/TranscriptOwnerCallStatusProvider.singleScan.test.ts +0 -86
  521. package/src/adapter/repositories/TranscriptOwnerCallStatusProvider.test.ts +0 -996
  522. package/src/adapter/repositories/TranscriptOwnerCallStatusProvider.ts +0 -396
  523. package/src/adapter/repositories/TranscriptRefusalTailStatusProvider.test.ts +0 -208
  524. package/src/adapter/repositories/TranscriptRefusalTailStatusProvider.ts +0 -80
  525. package/src/adapter/repositories/TranscriptSessionSubAgentActivityRepository.test.ts +0 -1674
  526. package/src/adapter/repositories/TranscriptSessionSubAgentActivityRepository.ts +0 -459
  527. package/src/adapter/repositories/clSessionScopeUnitName.test.ts +0 -76
  528. package/src/adapter/repositories/clSessionScopeUnitName.ts +0 -5
  529. package/src/adapter/repositories/clSessionScopeUnitNameFromCgroupContent.test.ts +0 -34
  530. package/src/adapter/repositories/clSessionScopeUnitNameFromCgroupContent.ts +0 -8
  531. package/src/adapter/repositories/githubGraphqlClient.test.ts +0 -479
  532. package/src/adapter/repositories/githubGraphqlClient.ts +0 -243
  533. package/src/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.test.ts +0 -5182
  534. package/src/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.ts +0 -3303
  535. package/src/adapter/repositories/issue/ApiV3IssueRepository.test.ts +0 -46
  536. package/src/adapter/repositories/issue/ApiV3IssueRepository.ts +0 -88
  537. package/src/adapter/repositories/issue/GraphqlProjectItemRepository.test.ts +0 -1776
  538. package/src/adapter/repositories/issue/GraphqlProjectItemRepository.ts +0 -1552
  539. package/src/adapter/repositories/issue/RestIssueRepository.test.ts +0 -393
  540. package/src/adapter/repositories/issue/RestIssueRepository.ts +0 -252
  541. package/src/adapter/repositories/issue/gitHubRawUrl.test.ts +0 -29
  542. package/src/adapter/repositories/issue/gitHubRawUrl.ts +0 -30
  543. package/src/adapter/repositories/issue/githubRateLimitRetry.test.ts +0 -167
  544. package/src/adapter/repositories/issue/githubRateLimitRetry.ts +0 -109
  545. package/src/adapter/repositories/localStorageCacheDirectory.test.ts +0 -81
  546. package/src/adapter/repositories/localStorageCacheDirectory.ts +0 -13
  547. package/src/adapter/repositories/projectFieldDefinition.test.ts +0 -191
  548. package/src/adapter/repositories/projectFieldDefinition.ts +0 -144
  549. package/src/adapter/repositories/utils.test.ts +0 -55
  550. package/src/adapter/repositories/utils.ts +0 -45
  551. package/src/domain/entities/ClaudeHandoverSession.ts +0 -12
  552. package/src/domain/entities/ClaudeMessageResponse.ts +0 -31
  553. package/src/domain/entities/ClaudeTokenUsage.ts +0 -17
  554. package/src/domain/entities/ClaudeWindowUsage.ts +0 -5
  555. package/src/domain/entities/Comment.ts +0 -5
  556. package/src/domain/entities/InteractiveLiveSession.ts +0 -6
  557. package/src/domain/entities/Issue.ts +0 -31
  558. package/src/domain/entities/KevReportWatermark.ts +0 -4
  559. package/src/domain/entities/LiveSessionActivitySnapshot.ts +0 -14
  560. package/src/domain/entities/LiveSessionOutputActivity.ts +0 -4
  561. package/src/domain/entities/LiveSessionProcessSnapshot.ts +0 -18
  562. package/src/domain/entities/LiveTmuxSession.ts +0 -4
  563. package/src/domain/entities/Member.ts +0 -3
  564. package/src/domain/entities/OutputDegeneration.ts +0 -10
  565. package/src/domain/entities/Project.ts +0 -88
  566. package/src/domain/entities/ProjectField.ts +0 -3
  567. package/src/domain/entities/ProjectFieldName.ts +0 -2
  568. package/src/domain/entities/ProjectFieldSingleSelect.ts +0 -8
  569. package/src/domain/entities/ProjectFieldSingleSelectOption.ts +0 -8
  570. package/src/domain/entities/RequiredProjectField.ts +0 -68
  571. package/src/domain/entities/SearchedIssue.ts +0 -11
  572. package/src/domain/entities/StoryObjectMap.ts +0 -12
  573. package/src/domain/entities/TokenExhaustionHandoverState.ts +0 -8
  574. package/src/domain/entities/WorkflowStatus.ts +0 -66
  575. package/src/domain/usecases/ActionAnnouncementUseCase.test.ts +0 -91
  576. package/src/domain/usecases/ActionAnnouncementUseCase.ts +0 -78
  577. package/src/domain/usecases/AgentDesignationLabelAdoptUseCase.test.ts +0 -222
  578. package/src/domain/usecases/AgentDesignationLabelAdoptUseCase.ts +0 -76
  579. package/src/domain/usecases/AnalyzeProblemByIssueUseCase.test.ts +0 -118
  580. package/src/domain/usecases/AnalyzeProblemByIssueUseCase.ts +0 -112
  581. package/src/domain/usecases/AnalyzeStoriesUseCase.test.ts +0 -82
  582. package/src/domain/usecases/AnalyzeStoriesUseCase.ts +0 -296
  583. package/src/domain/usecases/AssignNoAssigneeIssueToManagerUseCase.test.ts +0 -578
  584. package/src/domain/usecases/AssignNoAssigneeIssueToManagerUseCase.ts +0 -137
  585. package/src/domain/usecases/CONTRIBUTING_FOR_TEST.md +0 -259
  586. package/src/domain/usecases/ChangeStatusByStoryColorUseCase.test.ts +0 -437
  587. package/src/domain/usecases/ChangeStatusByStoryColorUseCase.ts +0 -80
  588. package/src/domain/usecases/ChangeTargetPullRequestApprover.test.ts +0 -248
  589. package/src/domain/usecases/ChangeTargetPullRequestApprover.ts +0 -69
  590. package/src/domain/usecases/CheckIssueReviewReadinessUseCase.test.ts +0 -411
  591. package/src/domain/usecases/CheckIssueReviewReadinessUseCase.ts +0 -142
  592. package/src/domain/usecases/ClearDependedIssueURLUseCase.test.ts +0 -1120
  593. package/src/domain/usecases/ClearDependedIssueURLUseCase.ts +0 -161
  594. package/src/domain/usecases/ClearPastNextActionDateHourUseCase.test.ts +0 -345
  595. package/src/domain/usecases/ClearPastNextActionDateHourUseCase.ts +0 -81
  596. package/src/domain/usecases/CliErrorReportUseCase.test.ts +0 -162
  597. package/src/domain/usecases/CliErrorReportUseCase.ts +0 -72
  598. package/src/domain/usecases/ConflictedIssueRevertUseCase.test.ts +0 -886
  599. package/src/domain/usecases/ConflictedIssueRevertUseCase.ts +0 -146
  600. package/src/domain/usecases/CreateEstimationIssueUseCase.test.ts +0 -227
  601. package/src/domain/usecases/CreateEstimationIssueUseCase.ts +0 -205
  602. package/src/domain/usecases/CreateNewStoryByLabelUseCase.test.ts +0 -1084
  603. package/src/domain/usecases/CreateNewStoryByLabelUseCase.ts +0 -146
  604. package/src/domain/usecases/DailySecurityScanUseCase.test.ts +0 -1709
  605. package/src/domain/usecases/DailySecurityScanUseCase.ts +0 -476
  606. package/src/domain/usecases/DefaultSilentSessionMessageComposer.test.ts +0 -533
  607. package/src/domain/usecases/DefaultSilentSessionMessageComposer.ts +0 -122
  608. package/src/domain/usecases/GetStoryObjectMapUseCase.test.ts +0 -330
  609. package/src/domain/usecases/GetStoryObjectMapUseCase.ts +0 -79
  610. package/src/domain/usecases/HandleScheduledEventUseCase.test.ts +0 -1913
  611. package/src/domain/usecases/HandleScheduledEventUseCase.ts +0 -846
  612. package/src/domain/usecases/InTmuxByHumanSessionTokenCountUseCase.test.ts +0 -175
  613. package/src/domain/usecases/InTmuxByHumanSessionTokenCountUseCase.ts +0 -63
  614. package/src/domain/usecases/IssueNoStatusUpdateUseCase.test.ts +0 -165
  615. package/src/domain/usecases/IssueNoStatusUpdateUseCase.ts +0 -44
  616. package/src/domain/usecases/IssueRejectionEvaluator.test.ts +0 -1137
  617. package/src/domain/usecases/IssueRejectionEvaluator.ts +0 -340
  618. package/src/domain/usecases/LiveSessionOauthTokenSelectUseCase.test.ts +0 -679
  619. package/src/domain/usecases/LiveSessionOauthTokenSelectUseCase.ts +0 -178
  620. package/src/domain/usecases/NotifyFinishedIssuePreparationUseCase.test.ts +0 -5441
  621. package/src/domain/usecases/NotifyFinishedIssuePreparationUseCase.ts +0 -891
  622. package/src/domain/usecases/NotifySilentLiveSessionsUseCase.test.ts +0 -2069
  623. package/src/domain/usecases/NotifySilentLiveSessionsUseCase.ts +0 -489
  624. package/src/domain/usecases/OauthTokenSelectUseCase.test.ts +0 -662
  625. package/src/domain/usecases/OauthTokenSelectUseCase.ts +0 -274
  626. package/src/domain/usecases/OutputDegenerationDetector.test.ts +0 -167
  627. package/src/domain/usecases/OutputDegenerationDetector.ts +0 -169
  628. package/src/domain/usecases/ProjectRequiredFieldCreateUseCase.test.ts +0 -711
  629. package/src/domain/usecases/ProjectRequiredFieldCreateUseCase.ts +0 -146
  630. package/src/domain/usecases/QualityCheckAdvanceUseCase.test.ts +0 -421
  631. package/src/domain/usecases/QualityCheckAdvanceUseCase.ts +0 -76
  632. package/src/domain/usecases/ReopenedDoneIssueRevertUseCase.test.ts +0 -188
  633. package/src/domain/usecases/ReopenedDoneIssueRevertUseCase.ts +0 -57
  634. package/src/domain/usecases/ResolveInteractiveLiveSessionsUseCase.test.ts +0 -303
  635. package/src/domain/usecases/ResolveInteractiveLiveSessionsUseCase.ts +0 -155
  636. package/src/domain/usecases/RevertNotReadyReviewQueueIssueUseCase.test.ts +0 -1838
  637. package/src/domain/usecases/RevertNotReadyReviewQueueIssueUseCase.ts +0 -221
  638. package/src/domain/usecases/RevertOrphanedPreparationUseCase.test.ts +0 -2392
  639. package/src/domain/usecases/RevertOrphanedPreparationUseCase.ts +0 -499
  640. package/src/domain/usecases/SessionOutputDegenerationRecoveryUseCase.test.ts +0 -224
  641. package/src/domain/usecases/SessionOutputDegenerationRecoveryUseCase.ts +0 -142
  642. package/src/domain/usecases/SetDependedIssueUrlForOpenTaskPRsUseCase.test.ts +0 -360
  643. package/src/domain/usecases/SetDependedIssueUrlForOpenTaskPRsUseCase.ts +0 -70
  644. package/src/domain/usecases/SetNoStoryIssueToStoryUseCase.test.ts +0 -400
  645. package/src/domain/usecases/SetNoStoryIssueToStoryUseCase.ts +0 -48
  646. package/src/domain/usecases/SetWorkflowManagementIssueToStoryUseCase.test.ts +0 -696
  647. package/src/domain/usecases/SetWorkflowManagementIssueToStoryUseCase.ts +0 -201
  648. package/src/domain/usecases/SetupTowerDefenceProjectUseCase.test.ts +0 -1363
  649. package/src/domain/usecases/SetupTowerDefenceProjectUseCase.ts +0 -215
  650. package/src/domain/usecases/StaleTaskPullRequestCloseUseCase.test.ts +0 -170
  651. package/src/domain/usecases/StaleTaskPullRequestCloseUseCase.ts +0 -38
  652. package/src/domain/usecases/StaleTmuxSessionKillUseCase.test.ts +0 -283
  653. package/src/domain/usecases/StaleTmuxSessionKillUseCase.ts +0 -98
  654. package/src/domain/usecases/StartPreparationUseCase.test.ts +0 -7822
  655. package/src/domain/usecases/StartPreparationUseCase.ts +0 -876
  656. package/src/domain/usecases/SubscriptionDisabledIssueUseCase.test.ts +0 -200
  657. package/src/domain/usecases/SubscriptionDisabledIssueUseCase.ts +0 -74
  658. package/src/domain/usecases/TokenExhaustionHandoverUseCase.test.ts +0 -838
  659. package/src/domain/usecases/TokenExhaustionHandoverUseCase.ts +0 -439
  660. package/src/domain/usecases/UpdateIssueStatusByLabelUseCase.test.ts +0 -306
  661. package/src/domain/usecases/UpdateIssueStatusByLabelUseCase.ts +0 -64
  662. package/src/domain/usecases/UpdateRateLimitCacheUseCase.test.ts +0 -178
  663. package/src/domain/usecases/UpdateRateLimitCacheUseCase.ts +0 -22
  664. package/src/domain/usecases/adapter-interfaces/ClaudeHandoverSessionRepository.ts +0 -5
  665. package/src/domain/usecases/adapter-interfaces/ClaudeInteractiveSessionRepository.ts +0 -9
  666. package/src/domain/usecases/adapter-interfaces/ClaudeLiveSessionRepository.ts +0 -8
  667. package/src/domain/usecases/adapter-interfaces/ClaudeMessageResponseRepository.ts +0 -5
  668. package/src/domain/usecases/adapter-interfaces/ClaudeRepository.ts +0 -6
  669. package/src/domain/usecases/adapter-interfaces/ClaudeTokenUsageRepository.ts +0 -8
  670. package/src/domain/usecases/adapter-interfaces/ConsoleTabsRepository.ts +0 -12
  671. package/src/domain/usecases/adapter-interfaces/CopilotRepository.ts +0 -3
  672. package/src/domain/usecases/adapter-interfaces/DateRepository.ts +0 -7
  673. package/src/domain/usecases/adapter-interfaces/GitHubGraphqlRateLimitRepository.ts +0 -3
  674. package/src/domain/usecases/adapter-interfaces/HttpRepository.ts +0 -3
  675. package/src/domain/usecases/adapter-interfaces/InteractiveLiveSessionTranscriptResolver.ts +0 -7
  676. package/src/domain/usecases/adapter-interfaces/IssueAttachmentRepository.ts +0 -9
  677. package/src/domain/usecases/adapter-interfaces/IssueCheckpointRepository.ts +0 -3
  678. package/src/domain/usecases/adapter-interfaces/IssueCommentRepository.ts +0 -7
  679. package/src/domain/usecases/adapter-interfaces/IssueRepository.ts +0 -229
  680. package/src/domain/usecases/adapter-interfaces/KevReportWatermarkRepository.ts +0 -11
  681. package/src/domain/usecases/adapter-interfaces/LiveSessionProcessSnapshotProvider.ts +0 -5
  682. package/src/domain/usecases/adapter-interfaces/LocalCommandRunner.ts +0 -16
  683. package/src/domain/usecases/adapter-interfaces/OwnerCallStatusProvider.ts +0 -5
  684. package/src/domain/usecases/adapter-interfaces/ProcessEnvironReader.ts +0 -3
  685. package/src/domain/usecases/adapter-interfaces/ProcessSignalRepository.ts +0 -5
  686. package/src/domain/usecases/adapter-interfaces/ProjectRepository.ts +0 -29
  687. package/src/domain/usecases/adapter-interfaces/RateLimitCacheRepository.ts +0 -10
  688. package/src/domain/usecases/adapter-interfaces/RefusalTailStatusProvider.ts +0 -5
  689. package/src/domain/usecases/adapter-interfaces/SessionAssistantTurnsRepository.ts +0 -6
  690. package/src/domain/usecases/adapter-interfaces/SessionDegenerationCooldownStateRepository.ts +0 -4
  691. package/src/domain/usecases/adapter-interfaces/SessionOutputActivityRepository.ts +0 -7
  692. package/src/domain/usecases/adapter-interfaces/SessionRecordReader.ts +0 -3
  693. package/src/domain/usecases/adapter-interfaces/SessionSubAgentActivityRepository.ts +0 -8
  694. package/src/domain/usecases/adapter-interfaces/SilentSessionCandidateStateRepository.ts +0 -10
  695. package/src/domain/usecases/adapter-interfaces/SilentSessionHubTaskStatusCacheRepository.ts +0 -20
  696. package/src/domain/usecases/adapter-interfaces/SilentSessionMessageComposer.ts +0 -18
  697. package/src/domain/usecases/adapter-interfaces/SilentSessionNotificationRepository.ts +0 -6
  698. package/src/domain/usecases/adapter-interfaces/SlackRepository.ts +0 -20
  699. package/src/domain/usecases/adapter-interfaces/Sleeper.ts +0 -3
  700. package/src/domain/usecases/adapter-interfaces/SpreadsheetRepository.ts +0 -18
  701. package/src/domain/usecases/adapter-interfaces/StatusDefaultRepository.ts +0 -5
  702. package/src/domain/usecases/adapter-interfaces/SubAgentLivenessResolver.ts +0 -6
  703. package/src/domain/usecases/adapter-interfaces/SubAgentProcessLister.ts +0 -8
  704. package/src/domain/usecases/adapter-interfaces/SubAgentSilentSecondsResolver.ts +0 -3
  705. package/src/domain/usecases/adapter-interfaces/SubAgentTranscriptDirectoryResolver.ts +0 -6
  706. package/src/domain/usecases/adapter-interfaces/TakeOwnershipSpawnRepository.ts +0 -9
  707. package/src/domain/usecases/adapter-interfaces/TmuxSessionRepository.ts +0 -20
  708. package/src/domain/usecases/adapter-interfaces/TokenExhaustionHandoverStateRepository.ts +0 -6
  709. package/src/domain/usecases/adapter-interfaces/TokenRateLimitSnapshotRepository.ts +0 -22
  710. package/src/domain/usecases/adapter-interfaces/WebhookRepository.ts +0 -3
  711. package/src/domain/usecases/agentReportPrefix.ts +0 -1
  712. package/src/domain/usecases/autoStatusCheckComments.ts +0 -1
  713. package/src/domain/usecases/console/.gitkeep +0 -0
  714. package/src/domain/usecases/console/GenerateConsoleListsUseCase.test.ts +0 -887
  715. package/src/domain/usecases/console/GenerateConsoleListsUseCase.ts +0 -368
  716. package/src/domain/usecases/dashboard/ComposeDashboardUseCase.test.ts +0 -654
  717. package/src/domain/usecases/dashboard/ComposeDashboardUseCase.ts +0 -318
  718. package/src/domain/usecases/dashboard/DashboardProjectCode.test.ts +0 -34
  719. package/src/domain/usecases/dashboard/DashboardProjectCode.ts +0 -27
  720. package/src/domain/usecases/dashboard/GenerateDashboardRowUseCase.test.ts +0 -160
  721. package/src/domain/usecases/dashboard/GenerateDashboardRowUseCase.ts +0 -69
  722. package/src/domain/usecases/dashboard/GenerateTokenStatusUseCase.test.ts +0 -362
  723. package/src/domain/usecases/dashboard/GenerateTokenStatusUseCase.ts +0 -234
  724. package/src/domain/usecases/ensureAgentOptionAndGetId.ts +0 -47
  725. package/src/domain/usecases/extractFencedJsonBlocks.test.ts +0 -40
  726. package/src/domain/usecases/extractFencedJsonBlocks.ts +0 -21
  727. package/src/domain/usecases/extractNextStepAgent.test.ts +0 -58
  728. package/src/domain/usecases/extractNextStepAgent.ts +0 -18
  729. package/src/domain/usecases/extractNextStepAgentFromComments.ts +0 -13
  730. package/src/domain/usecases/findLastAgentReport.test.ts +0 -121
  731. package/src/domain/usecases/findLastAgentReport.ts +0 -14
  732. package/src/domain/usecases/intmux/GenerateInTmuxByHumanDataUseCase.test.ts +0 -339
  733. package/src/domain/usecases/intmux/GenerateInTmuxByHumanDataUseCase.ts +0 -188
  734. package/src/domain/usecases/intmux/InTmuxByHumanSessionReconcileUseCase.test.ts +0 -389
  735. package/src/domain/usecases/intmux/InTmuxByHumanSessionReconcileUseCase.ts +0 -103
  736. package/src/domain/usecases/intmux/OwnerCallFile.test.ts +0 -184
  737. package/src/domain/usecases/intmux/OwnerCallFile.ts +0 -80
  738. package/src/domain/usecases/isAgentReportBody.test.ts +0 -108
  739. package/src/domain/usecases/isAgentReportBody.ts +0 -52
  740. package/src/domain/usecases/isAuthorAuthorizedForAutoStatusCheck.ts +0 -12
  741. package/src/domain/usecases/isHumanComment.test.ts +0 -77
  742. package/src/domain/usecases/isHumanComment.ts +0 -23
  743. package/src/domain/usecases/isRecord.ts +0 -2
  744. package/src/domain/usecases/issueReactivationTriggerIsPending.test.ts +0 -541
  745. package/src/domain/usecases/issueReactivationTriggerIsPending.ts +0 -25
  746. package/src/domain/usecases/nextStepAgentDispatchRepeatedMessage.ts +0 -2
  747. package/src/domain/usecases/normalizeReportBody.test.ts +0 -24
  748. package/src/domain/usecases/normalizeReportBody.ts +0 -2
  749. package/src/domain/usecases/resolveAllowedIssueAuthors.test.ts +0 -51
  750. package/src/domain/usecases/resolveAllowedIssueAuthors.ts +0 -6
  751. package/src/domain/usecases/resolveLabelsAsLlmAgentName.test.ts +0 -42
  752. package/src/domain/usecases/resolveLabelsAsLlmAgentName.ts +0 -6
  753. package/src/domain/usecases/resolveLabelsNotRequiringPullRequest.test.ts +0 -33
  754. package/src/domain/usecases/resolveLabelsNotRequiringPullRequest.ts +0 -7
  755. package/src/domain/usecases/resolveNextStepAgentDispatchRepetition.test.ts +0 -337
  756. package/src/domain/usecases/resolveNextStepAgentDispatchRepetition.ts +0 -133
  757. package/src/domain/usecases/silentSessionReminderSentinel.ts +0 -9
  758. package/src/domain/usecases/triagerAgentName.ts +0 -4
  759. package/src/domain/usecases/utils.test.ts +0 -35
  760. package/src/domain/usecases/utils.ts +0 -6
  761. package/src/index.test.ts +0 -8
  762. package/src/index.ts +0 -10
  763. package/src/releaseConfig.test.ts +0 -40
  764. package/tsconfig.build.json +0 -18
  765. package/tsconfig.json +0 -25
  766. package/types/adapter/entry-points/handlers/resolveUnansweredOwnerCalls.d.ts +0 -13
  767. package/types/adapter/entry-points/handlers/resolveUnansweredOwnerCalls.d.ts.map +0 -1
  768. package/types/adapter/repositories/FileSystemSilentSessionNotifiedStateRepository.d.ts +0 -16
  769. package/types/adapter/repositories/FileSystemSilentSessionNotifiedStateRepository.d.ts.map +0 -1
  770. package/types/adapter/repositories/issue/issueTimelineUtils.d.ts +0 -12
  771. package/types/adapter/repositories/issue/issueTimelineUtils.d.ts.map +0 -1
  772. package/types/domain/entities/UnansweredOwnerCall.d.ts +0 -5
  773. package/types/domain/entities/UnansweredOwnerCall.d.ts.map +0 -1
  774. package/types/domain/entities/WorkingTime.d.ts +0 -8
  775. package/types/domain/entities/WorkingTime.d.ts.map +0 -1
  776. package/types/domain/usecases/ChangeStatusLongInReviewIssueUseCase.d.ts +0 -17
  777. package/types/domain/usecases/ChangeStatusLongInReviewIssueUseCase.d.ts.map +0 -1
  778. package/types/domain/usecases/ConvertCheckboxToIssueInStoryIssueUseCase.d.ts +0 -23
  779. package/types/domain/usecases/ConvertCheckboxToIssueInStoryIssueUseCase.d.ts.map +0 -1
  780. package/types/domain/usecases/GenerateWorkingTimeReportUseCase.d.ts +0 -50
  781. package/types/domain/usecases/GenerateWorkingTimeReportUseCase.d.ts.map +0 -1
  782. package/types/domain/usecases/TriagerApprovalDispatchUseCase.d.ts +0 -15
  783. package/types/domain/usecases/TriagerApprovalDispatchUseCase.d.ts.map +0 -1
  784. package/types/domain/usecases/adapter-interfaces/SilentSessionNotifiedStateRepository.d.ts +0 -11
  785. package/types/domain/usecases/adapter-interfaces/SilentSessionNotifiedStateRepository.d.ts.map +0 -1
  786. package/types/domain/usecases/awaitingOwnerApprovalMessage.d.ts +0 -3
  787. package/types/domain/usecases/awaitingOwnerApprovalMessage.d.ts.map +0 -1
  788. package/types/domain/usecases/findLastAgentDeclaringReport.d.ts +0 -5
  789. package/types/domain/usecases/findLastAgentDeclaringReport.d.ts.map +0 -1
  790. package/types/domain/usecases/isPullRequestDeclaredUnnecessary.d.ts +0 -5
  791. package/types/domain/usecases/isPullRequestDeclaredUnnecessary.d.ts.map +0 -1
  792. package/types/domain/usecases/isWaitingForOwnerApproval.d.ts +0 -2
  793. package/types/domain/usecases/isWaitingForOwnerApproval.d.ts.map +0 -1
  794. package/types/domain/usecases/returnedToAwaitingWorkspaceMessage.d.ts +0 -3
  795. package/types/domain/usecases/returnedToAwaitingWorkspaceMessage.d.ts.map +0 -1
@@ -1,3303 +0,0 @@
1
- import {
2
- IssueRepository,
3
- RelatedPullRequest,
4
- OpenPullRequestCiStatus,
5
- IssueComment,
6
- PullRequestDetail,
7
- PullRequestFile,
8
- PullRequestCommit,
9
- PullRequestReviewCommentSide,
10
- PullRequestReviewInlineLocation,
11
- } from '../../../domain/usecases/adapter-interfaces/IssueRepository';
12
- import { FieldOption, Project } from '../../../domain/entities/Project';
13
- import { Issue } from '../../../domain/entities/Issue';
14
- import { SearchedIssue } from '../../../domain/entities/SearchedIssue';
15
- import { StoryObjectMap } from '../../../domain/entities/StoryObjectMap';
16
- import { ApiV3IssueRepository } from './ApiV3IssueRepository';
17
- import { normalizeGitHubRawUrl } from './gitHubRawUrl';
18
- import { RestIssueRepository } from './RestIssueRepository';
19
- import {
20
- GraphqlProjectItemRepository,
21
- ProjectItem,
22
- } from './GraphqlProjectItemRepository';
23
- import { LocalStorageCacheRepository } from '../LocalStorageCacheRepository';
24
- import {
25
- CachedProjectIssues,
26
- isIssueArray,
27
- isProject,
28
- ProjectIssuesCacheRepository,
29
- } from '../ProjectIssuesCacheRepository';
30
- import { BaseGitHubRepository } from '../BaseGitHubRepository';
31
- import {
32
- fetchGithubGraphql,
33
- postGithubGraphqlJson,
34
- } from '../githubGraphqlClient';
35
- import { normalizeFieldName } from '../utils';
36
- import { LocalStorageRepository } from '../LocalStorageRepository';
37
- import { Member } from '../../../domain/entities/Member';
38
- import { ProjectRepository } from '../../../domain/usecases/adapter-interfaces/ProjectRepository';
39
- import { DateRepository } from '../../../domain/usecases/adapter-interfaces/DateRepository';
40
- import {
41
- Sleep,
42
- realSleep,
43
- fetchWithGitHubRateLimitRetry,
44
- computeRateLimitResetIso,
45
- hasRateLimitSignals,
46
- GitHubRateLimitError,
47
- } from './githubRateLimitRetry';
48
-
49
- export const FULL_ISSUE_FETCH_INTERVAL_MS = 60 * 60 * 1000;
50
- export const INCREMENTAL_FETCH_SKEW_BUFFER_MS = 5 * 60 * 1000;
51
- export const REQUIRED_CHECKS_CACHE_TTL_MS = 10 * 60 * 1000;
52
-
53
- const SELF_AUTHORED_REVIEW_REFUSAL =
54
- 'Can not request changes on your own pull request';
55
-
56
- type TimelineItem = {
57
- __typename: string;
58
- willCloseTarget?: boolean;
59
- source?: {
60
- __typename: string;
61
- url?: string;
62
- number?: number;
63
- body?: string | null;
64
- state?: string;
65
- createdAt?: string;
66
- isDraft?: boolean;
67
- mergeable?: string;
68
- headRefName?: string;
69
- baseRefName?: string;
70
- baseRef?: {
71
- name: string;
72
- } | null;
73
- };
74
- };
75
-
76
- type IssueTimelineResponse = {
77
- data?: {
78
- repository?: {
79
- issue?: {
80
- timelineItems: {
81
- pageInfo: {
82
- endCursor: string;
83
- hasNextPage: boolean;
84
- };
85
- nodes: TimelineItem[];
86
- };
87
- };
88
- };
89
- };
90
- errors?: Array<{ message: string }>;
91
- };
92
-
93
- type CiContextNode =
94
- | {
95
- __typename: 'CheckRun';
96
- name: string;
97
- conclusion: string | null;
98
- databaseId: number;
99
- }
100
- | {
101
- __typename: 'StatusContext';
102
- context: string;
103
- state: string;
104
- };
105
-
106
- type PrStatusComputationData = {
107
- isDraft?: boolean;
108
- mergeable?: string;
109
- requiredCheckNames: string[];
110
- ciContexts: CiContextNode[];
111
- reviewThreads: Array<{
112
- isResolved: boolean;
113
- }>;
114
- };
115
-
116
- // One GraphQL query carrying this many aliased pull requests costs the same
117
- // single rate-limit point as a query carrying one, measured against the live
118
- // API by reading the rateLimit cost field it returns.
119
- const SLIM_PULL_REQUEST_BATCH_SIZE = 100;
120
- const SLIM_PULL_REQUEST_REVIEW_THREADS_PAGE_SIZE = 100;
121
-
122
- type SlimPullRequestNodeResponse = {
123
- url: string;
124
- state: string;
125
- isDraft?: boolean;
126
- headRefName?: string;
127
- baseRefName?: string;
128
- mergeable?: string;
129
- headRefOid?: string;
130
- reviewThreads?: {
131
- pageInfo: {
132
- endCursor: string | null;
133
- hasNextPage: boolean;
134
- };
135
- nodes: Array<{
136
- isResolved: boolean;
137
- }>;
138
- };
139
- };
140
-
141
- type SlimPullRequestBatchResponse = {
142
- data?: Record<
143
- string,
144
- { pullRequest?: SlimPullRequestNodeResponse | null } | null
145
- > | null;
146
- errors?: Array<{
147
- message: string;
148
- type?: string;
149
- path?: Array<string | number>;
150
- }>;
151
- };
152
-
153
- type SlimPullRequestResponse = {
154
- data?: {
155
- repository?: {
156
- pullRequest?: {
157
- url: string;
158
- state: string;
159
- isDraft?: boolean;
160
- headRefName?: string;
161
- baseRefName?: string;
162
- mergeable?: string;
163
- headRefOid?: string;
164
- reviewThreads?: {
165
- pageInfo: {
166
- endCursor: string | null;
167
- hasNextPage: boolean;
168
- };
169
- nodes: Array<{
170
- isResolved: boolean;
171
- }>;
172
- };
173
- } | null;
174
- } | null;
175
- };
176
- errors?: Array<{ message: string }>;
177
- };
178
-
179
- type SlimPullRequest = {
180
- url: string;
181
- state: string;
182
- isDraft?: boolean;
183
- headRefName?: string;
184
- baseRefName?: string;
185
- mergeable?: string;
186
- headRefOid?: string;
187
- reviewThreads: Array<{
188
- isResolved: boolean;
189
- }>;
190
- };
191
-
192
- type BranchRulesResponseItem = {
193
- type: string;
194
- parameters?: {
195
- required_status_checks?: Array<{
196
- context: string;
197
- }>;
198
- };
199
- };
200
-
201
- type BranchDetailResponse = {
202
- protection?: {
203
- required_status_checks?: {
204
- contexts?: string[];
205
- } | null;
206
- } | null;
207
- };
208
-
209
- type CheckRunsResponse = {
210
- total_count: number;
211
- check_runs: Array<{
212
- id: number;
213
- name: string;
214
- conclusion: string | null;
215
- }>;
216
- };
217
-
218
- type CheckSuitesResponse = {
219
- total_count: number;
220
- check_suites: Array<{
221
- id: number;
222
- }>;
223
- };
224
-
225
- type CombinedStatusResponse = {
226
- statuses: Array<{
227
- context: string;
228
- state: string;
229
- }>;
230
- };
231
-
232
- type PullRequestMergeabilityResponse = {
233
- data?: {
234
- repository?: {
235
- pullRequest?: {
236
- mergeable?: string | null;
237
- mergeStateStatus?: string | null;
238
- } | null;
239
- } | null;
240
- };
241
- errors?: Array<{ message: string }>;
242
- };
243
-
244
- function isIssueTimelineResponse(
245
- value: unknown,
246
- ): value is IssueTimelineResponse {
247
- if (typeof value !== 'object' || value === null) return false;
248
- return true;
249
- }
250
-
251
- function isSlimPullRequestResponse(
252
- value: unknown,
253
- ): value is SlimPullRequestResponse {
254
- if (typeof value !== 'object' || value === null) return false;
255
- return true;
256
- }
257
-
258
- function isSlimPullRequestBatchResponse(
259
- value: unknown,
260
- ): value is SlimPullRequestBatchResponse {
261
- if (typeof value !== 'object' || value === null) return false;
262
- return true;
263
- }
264
-
265
- function isBranchRulesResponse(
266
- value: unknown,
267
- ): value is BranchRulesResponseItem[] {
268
- return Array.isArray(value);
269
- }
270
-
271
- function isBranchDetailResponse(value: unknown): value is BranchDetailResponse {
272
- if (typeof value !== 'object' || value === null) return false;
273
- return true;
274
- }
275
-
276
- function isCheckRunsResponse(value: unknown): value is CheckRunsResponse {
277
- if (typeof value !== 'object' || value === null) return false;
278
- return 'check_runs' in value && Array.isArray(value.check_runs);
279
- }
280
-
281
- function isCheckSuitesResponse(value: unknown): value is CheckSuitesResponse {
282
- if (typeof value !== 'object' || value === null) return false;
283
- return 'check_suites' in value && Array.isArray(value.check_suites);
284
- }
285
-
286
- function isCombinedStatusResponse(
287
- value: unknown,
288
- ): value is CombinedStatusResponse {
289
- if (typeof value !== 'object' || value === null) return false;
290
- return 'statuses' in value && Array.isArray(value.statuses);
291
- }
292
-
293
- type AuthenticatedUserResponse = {
294
- login: string;
295
- };
296
-
297
- function isAuthenticatedUserResponse(
298
- value: unknown,
299
- ): value is AuthenticatedUserResponse {
300
- if (typeof value !== 'object' || value === null) return false;
301
- if (!('login' in value)) return false;
302
- return typeof value.login === 'string';
303
- }
304
-
305
- function isPullRequestMergeabilityResponse(
306
- value: unknown,
307
- ): value is PullRequestMergeabilityResponse {
308
- if (typeof value !== 'object' || value === null) return false;
309
- return true;
310
- }
311
-
312
- type PullRequestFilesResponseItem = {
313
- filename: string;
314
- };
315
-
316
- function isPullRequestFilesResponse(
317
- value: unknown,
318
- ): value is PullRequestFilesResponseItem[] {
319
- if (!Array.isArray(value)) return false;
320
- return value.every(
321
- (item) => typeof item === 'object' && item !== null && 'filename' in item,
322
- );
323
- }
324
-
325
- function isRecord(value: unknown): value is Record<string, unknown> {
326
- return typeof value === 'object' && value !== null;
327
- }
328
-
329
- type RestPullRequestCiStatusResponse = {
330
- html_url: string;
331
- state: string;
332
- draft: boolean;
333
- mergeable: boolean | null;
334
- head: { ref: string; sha: string };
335
- base: { ref: string };
336
- };
337
-
338
- function isRestPullRequestCiStatusResponse(
339
- value: unknown,
340
- ): value is RestPullRequestCiStatusResponse {
341
- if (!isRecord(value)) return false;
342
- const head: unknown = value.head;
343
- const base: unknown = value.base;
344
- return (
345
- typeof value.html_url === 'string' &&
346
- typeof value.state === 'string' &&
347
- typeof value.draft === 'boolean' &&
348
- (value.mergeable === null || typeof value.mergeable === 'boolean') &&
349
- isRecord(head) &&
350
- typeof head.ref === 'string' &&
351
- typeof head.sha === 'string' &&
352
- isRecord(base) &&
353
- typeof base.ref === 'string'
354
- );
355
- }
356
-
357
- export const graphqlMergeableFromRestMergeable = (
358
- mergeable: boolean | null,
359
- ): string => {
360
- if (mergeable === true) {
361
- return 'MERGEABLE';
362
- }
363
- if (mergeable === false) {
364
- return 'CONFLICTING';
365
- }
366
- return 'UNKNOWN';
367
- };
368
-
369
- function isNullableString(value: unknown): value is string | null {
370
- return value === null || typeof value === 'string';
371
- }
372
-
373
- function isLoginContainer(value: unknown): value is { login: string } {
374
- return isRecord(value) && typeof value.login === 'string';
375
- }
376
-
377
- function isRefContainer(value: unknown): value is { ref: string } {
378
- return isRecord(value) && typeof value.ref === 'string';
379
- }
380
-
381
- function isRepoMergeSettings(
382
- value: unknown,
383
- ): value is { allow_squash_merge?: boolean; allow_rebase_merge?: boolean } {
384
- return isRecord(value);
385
- }
386
-
387
- type IssueOrPullRequestBodyResponse = {
388
- body: string | null;
389
- };
390
-
391
- function isIssueOrPullRequestBodyResponse(
392
- value: unknown,
393
- ): value is IssueOrPullRequestBodyResponse {
394
- return isRecord(value) && isNullableString(value.body);
395
- }
396
-
397
- type IssueOrPullRequestStateResponse = {
398
- state: string;
399
- title: string;
400
- };
401
-
402
- function isIssueOrPullRequestStateResponse(
403
- value: unknown,
404
- ): value is IssueOrPullRequestStateResponse {
405
- return (
406
- isRecord(value) &&
407
- typeof value.state === 'string' &&
408
- typeof value.title === 'string'
409
- );
410
- }
411
-
412
- type IssueCommentsResponseItem = {
413
- user: { login: string } | null;
414
- body: string | null;
415
- created_at: string;
416
- };
417
-
418
- function isIssueCommentsResponseItem(
419
- value: unknown,
420
- ): value is IssueCommentsResponseItem {
421
- if (!isRecord(value)) return false;
422
- const userValid = value.user === null || isLoginContainer(value.user);
423
- return (
424
- userValid &&
425
- isNullableString(value.body) &&
426
- typeof value.created_at === 'string'
427
- );
428
- }
429
-
430
- function isIssueCommentsResponse(
431
- value: unknown,
432
- ): value is IssueCommentsResponseItem[] {
433
- return Array.isArray(value) && value.every(isIssueCommentsResponseItem);
434
- }
435
-
436
- type IssueCommentIdResponseItem = {
437
- id: number;
438
- };
439
-
440
- function isIssueCommentIdResponseItem(
441
- value: unknown,
442
- ): value is IssueCommentIdResponseItem {
443
- return isRecord(value) && typeof value.id === 'number';
444
- }
445
-
446
- function isIssueCommentIdResponse(
447
- value: unknown,
448
- ): value is IssueCommentIdResponseItem[] {
449
- return Array.isArray(value) && value.every(isIssueCommentIdResponseItem);
450
- }
451
-
452
- type PullRequestDetailResponse = {
453
- title: string;
454
- state: string;
455
- merged: boolean;
456
- draft: boolean;
457
- additions: number;
458
- deletions: number;
459
- changed_files: number;
460
- head: { ref: string };
461
- base: { ref: string };
462
- user: { login: string } | null;
463
- body: string | null;
464
- };
465
-
466
- function isPullRequestDetailResponse(
467
- value: unknown,
468
- ): value is PullRequestDetailResponse {
469
- if (!isRecord(value)) return false;
470
- const userValid = value.user === null || isLoginContainer(value.user);
471
- return (
472
- typeof value.title === 'string' &&
473
- typeof value.state === 'string' &&
474
- typeof value.merged === 'boolean' &&
475
- typeof value.draft === 'boolean' &&
476
- typeof value.additions === 'number' &&
477
- typeof value.deletions === 'number' &&
478
- typeof value.changed_files === 'number' &&
479
- isRefContainer(value.head) &&
480
- isRefContainer(value.base) &&
481
- userValid &&
482
- isNullableString(value.body)
483
- );
484
- }
485
-
486
- type PullRequestDetailFilesResponseItem = {
487
- filename: string;
488
- status: string;
489
- additions: number;
490
- deletions: number;
491
- patch?: string;
492
- raw_url?: string;
493
- };
494
-
495
- function isPullRequestDetailFilesResponseItem(
496
- value: unknown,
497
- ): value is PullRequestDetailFilesResponseItem {
498
- if (!isRecord(value)) return false;
499
- return (
500
- typeof value.filename === 'string' &&
501
- typeof value.status === 'string' &&
502
- typeof value.additions === 'number' &&
503
- typeof value.deletions === 'number' &&
504
- (value.patch === undefined || typeof value.patch === 'string') &&
505
- (value.raw_url === undefined || typeof value.raw_url === 'string')
506
- );
507
- }
508
-
509
- function isPullRequestDetailFilesResponse(
510
- value: unknown,
511
- ): value is PullRequestDetailFilesResponseItem[] {
512
- return (
513
- Array.isArray(value) && value.every(isPullRequestDetailFilesResponseItem)
514
- );
515
- }
516
-
517
- type PullRequestCommitsResponseItem = {
518
- sha: string;
519
- commit: {
520
- message: string;
521
- author: { name: string; date: string } | null;
522
- };
523
- };
524
-
525
- function isCommitAuthor(
526
- value: unknown,
527
- ): value is { name: string; date: string } {
528
- return (
529
- isRecord(value) &&
530
- typeof value.name === 'string' &&
531
- typeof value.date === 'string'
532
- );
533
- }
534
-
535
- function isPullRequestCommitsResponseItem(
536
- value: unknown,
537
- ): value is PullRequestCommitsResponseItem {
538
- if (!isRecord(value)) return false;
539
- if (typeof value.sha !== 'string') return false;
540
- if (!isRecord(value.commit)) return false;
541
- if (typeof value.commit.message !== 'string') return false;
542
- return value.commit.author === null || isCommitAuthor(value.commit.author);
543
- }
544
-
545
- function isPullRequestCommitsResponse(
546
- value: unknown,
547
- ): value is PullRequestCommitsResponseItem[] {
548
- return Array.isArray(value) && value.every(isPullRequestCommitsResponseItem);
549
- }
550
-
551
- export class ApiV3CheerioRestIssueRepository
552
- extends BaseGitHubRepository
553
- implements IssueRepository
554
- {
555
- constructor(
556
- readonly apiV3IssueRepository: Pick<ApiV3IssueRepository, 'searchIssue'>,
557
- readonly restIssueRepository: Pick<
558
- RestIssueRepository,
559
- | 'createNewIssue'
560
- | 'updateIssue'
561
- | 'updateIssueBody'
562
- | 'createComment'
563
- | 'getIssue'
564
- | 'updateLabels'
565
- | 'removeLabel'
566
- | 'updateAssigneeList'
567
- | 'searchIssues'
568
- | 'getOrCreateLabel'
569
- >,
570
- readonly graphqlProjectItemRepository: Pick<
571
- GraphqlProjectItemRepository,
572
- | 'fetchProjectItems'
573
- | 'fetchProjectItemsLight'
574
- | 'fetchProjectItemsByIds'
575
- | 'fetchProjectItemByUrl'
576
- | 'updateProjectField'
577
- | 'clearProjectField'
578
- | 'updateProjectTextField'
579
- | 'addIssueToProject'
580
- >,
581
- readonly localStorageCacheRepository: Pick<
582
- LocalStorageCacheRepository,
583
- 'getSingle' | 'setSingle'
584
- >,
585
- readonly projectRepository: Pick<ProjectRepository, 'getProject'>,
586
- readonly dateRepository: DateRepository,
587
- readonly localStorageRepository: LocalStorageRepository,
588
- readonly ghToken: string = process.env.GH_TOKEN || 'dummy',
589
- readonly sleep: Sleep = realSleep,
590
- ) {
591
- super(localStorageRepository, ghToken);
592
- this.projectIssuesCacheRepository = new ProjectIssuesCacheRepository(
593
- localStorageCacheRepository,
594
- );
595
- }
596
-
597
- private readonly projectIssuesCacheRepository: ProjectIssuesCacheRepository;
598
-
599
- private readonly getAllIssuesRefreshMemo = new Map<
600
- Project['id'],
601
- { issues: Issue[]; project: Project; cacheUsed: boolean }
602
- >();
603
-
604
- private readonly lastIssuesFetchedAtByProjectId = new Map<
605
- Project['id'],
606
- string
607
- >();
608
-
609
- getLastIssuesFetchedAt = (projectId: Project['id']): string | null =>
610
- this.lastIssuesFetchedAtByProjectId.get(projectId) ?? null;
611
-
612
- private fetchWithRateLimitRetry = (
613
- request: () => Promise<Response>,
614
- ): Promise<Response> => fetchWithGitHubRateLimitRetry(request, this.sleep);
615
-
616
- private throwGitHubError = async (
617
- prefix: string,
618
- response: Response,
619
- ): Promise<never> => {
620
- const bodyText = await response.clone().text();
621
- const isRateLimit = hasRateLimitSignals(
622
- response.status,
623
- response.headers,
624
- bodyText,
625
- );
626
- const formatted = await this.formatGitHubErrorWithStatus(response);
627
- const message = `${prefix}: ${formatted}`;
628
- if (isRateLimit) {
629
- throw new GitHubRateLimitError(message);
630
- }
631
- throw new Error(message);
632
- };
633
-
634
- updateStatus: (
635
- project: Project,
636
- issue: Issue,
637
- statusId: string,
638
- ) => Promise<void> = async (project, issue, statusId) => {
639
- await this.graphqlProjectItemRepository.updateProjectField(
640
- project.id,
641
- project.status.fieldId,
642
- issue.itemId,
643
- { singleSelectOptionId: statusId },
644
- );
645
- };
646
-
647
- convertProjectItemToIssue = (item: ProjectItem): Issue => {
648
- const nextActionDate = item.customFields.find(
649
- (field) => normalizeFieldName(field.name) === 'nextactiondate',
650
- )?.value;
651
- const nextActionHour = item.customFields.find(
652
- (field) => normalizeFieldName(field.name) === 'nextactionhour',
653
- )?.value;
654
- const estimationMinutes = item.customFields.find(
655
- (field) => normalizeFieldName(field.name) === 'estimationminutes',
656
- )?.value;
657
- const dependedIssueUrls =
658
- item.customFields
659
- .find((field) =>
660
- normalizeFieldName(field.name).startsWith('dependedissueurls'),
661
- )
662
- ?.value?.split(',')
663
- .map((url) => url.trim())
664
- .filter((url) => url.length > 0) || [];
665
- const completionDate50PercentConfidence = item.customFields.find((field) =>
666
- normalizeFieldName(field.name).startsWith('completiondate50'),
667
- )?.value;
668
- const story = item.customFields.find(
669
- (field) => normalizeFieldName(field.name) === 'story',
670
- )?.value;
671
- const status = item.customFields.find(
672
- (field) => normalizeFieldName(field.name) === 'status',
673
- )?.value;
674
- const agent =
675
- item.customFields.find(
676
- (field) => normalizeFieldName(field.name) === 'agent',
677
- )?.value ?? null;
678
- const { owner, repo } = this.extractIssueFromUrl(item.url);
679
-
680
- return {
681
- nameWithOwner: item.nameWithOwner,
682
- url: item.url,
683
- title: item.title,
684
- number: item.number,
685
- state: item.state,
686
- labels: item.labels,
687
- assignees: item.assignees,
688
- nextActionDate: nextActionDate ? new Date(nextActionDate) : null,
689
- nextActionHour: nextActionHour ? parseInt(nextActionHour) : null,
690
- estimationMinutes: estimationMinutes ? parseInt(estimationMinutes) : null,
691
- dependedIssueUrls: dependedIssueUrls,
692
- completionDate50PercentConfidence: completionDate50PercentConfidence
693
- ? new Date(completionDate50PercentConfidence)
694
- : null,
695
- status: status || null,
696
- story: story || null,
697
- org: owner,
698
- repo: repo,
699
- body: item.body ?? '',
700
- itemId: item.id,
701
- isPr: item.url.includes('/pull/'),
702
- isInProgress: normalizeFieldName(status || '').includes('progress'),
703
- isClosed: item.state !== 'OPEN',
704
- createdAt: new Date(item.createdAt || '2000-01-01'),
705
- author: item.author,
706
- closingIssueReferenceUrls: item.closingIssueReferenceUrls,
707
- agent,
708
- isRepoArchived: item.isRepoArchived,
709
- stateReason: item.stateReason,
710
- };
711
- };
712
- private restoreIssuesFromCache = (rawIssues: unknown): Issue[] | null => {
713
- if (!Array.isArray(rawIssues)) {
714
- return null;
715
- }
716
- const issues = rawIssues
717
- .filter(
718
- (issue: unknown): issue is object =>
719
- typeof issue === 'object' && issue !== null,
720
- )
721
- .map((issue: object): object => {
722
- const nextActionDate =
723
- !('nextActionDate' in issue) ||
724
- typeof issue.nextActionDate !== 'string' ||
725
- issue.nextActionDate === null
726
- ? null
727
- : new Date(issue.nextActionDate);
728
- const completionDate50PercentConfidence =
729
- !('completionDate50PercentConfidence' in issue) ||
730
- typeof issue.completionDate50PercentConfidence !== 'string'
731
- ? null
732
- : new Date(issue.completionDate50PercentConfidence);
733
- const createdAt =
734
- !('createdAt' in issue) || typeof issue.createdAt !== 'string'
735
- ? new Date()
736
- : new Date(issue.createdAt);
737
- const closingIssueReferenceUrls =
738
- 'closingIssueReferenceUrls' in issue &&
739
- Array.isArray(issue.closingIssueReferenceUrls) &&
740
- issue.closingIssueReferenceUrls.every(
741
- (url): url is string => typeof url === 'string',
742
- )
743
- ? issue.closingIssueReferenceUrls
744
- : [];
745
-
746
- const stateReason =
747
- 'stateReason' in issue &&
748
- (issue.stateReason === 'COMPLETED' ||
749
- issue.stateReason === 'NOT_PLANNED' ||
750
- issue.stateReason === 'REOPENED')
751
- ? issue.stateReason
752
- : null;
753
- return {
754
- ...issue,
755
- nextActionDate: nextActionDate,
756
- completionDate50PercentConfidence: completionDate50PercentConfidence,
757
- createdAt: createdAt,
758
- closingIssueReferenceUrls: closingIssueReferenceUrls,
759
- stateReason: stateReason,
760
- };
761
- });
762
-
763
- if (isIssueArray(issues)) {
764
- return issues;
765
- }
766
- return null;
767
- };
768
-
769
- private readCachedProjectIssues = async (
770
- projectId: Project['id'],
771
- ): Promise<CachedProjectIssues | null> => {
772
- const raw = await this.projectIssuesCacheRepository.readRaw(projectId);
773
- if (typeof raw !== 'object' || raw === null) {
774
- return null;
775
- }
776
- if (
777
- !('lastFetchedAt' in raw) ||
778
- typeof raw.lastFetchedAt !== 'string' ||
779
- !('lastFullFetchAt' in raw) ||
780
- typeof raw.lastFullFetchAt !== 'string' ||
781
- !('project' in raw) ||
782
- !('issues' in raw)
783
- ) {
784
- return null;
785
- }
786
- if (!isProject(raw.project)) {
787
- return null;
788
- }
789
- const issues = this.restoreIssuesFromCache(raw.issues);
790
- if (!issues) {
791
- return null;
792
- }
793
- return {
794
- lastFetchedAt: raw.lastFetchedAt,
795
- lastFullFetchAt: raw.lastFullFetchAt,
796
- project: raw.project,
797
- issues,
798
- };
799
- };
800
-
801
- // Reads the Project (status/story option ids and field ids) that the TDPM
802
- // daemon persisted into the `allIssues-${projectId}` cache, without any
803
- // GraphQL call. Returns null on cache miss so callers can fall back to a
804
- // GraphQL project load only when the daemon has not populated the cache yet.
805
- getCachedProject = async (
806
- projectId: Project['id'],
807
- ): Promise<Project | null> =>
808
- this.projectIssuesCacheRepository.readProject(projectId);
809
-
810
- private toDateString = (date: Date): string =>
811
- `${date.getUTCFullYear()}-${String(date.getUTCMonth() + 1).padStart(2, '0')}-${String(date.getUTCDate()).padStart(2, '0')}`;
812
-
813
- getAllIssues = async (
814
- projectId: Project['id'],
815
- ): Promise<{
816
- issues: Issue[];
817
- project: Project;
818
- cacheUsed: boolean;
819
- }> => {
820
- const memoized = this.getAllIssuesRefreshMemo.get(projectId);
821
- if (memoized) {
822
- return memoized;
823
- }
824
- const result = await this.refreshAllIssues(projectId);
825
- this.getAllIssuesRefreshMemo.set(projectId, result);
826
- return result;
827
- };
828
-
829
- private refreshAllIssues = async (
830
- projectId: Project['id'],
831
- ): Promise<{ issues: Issue[]; project: Project; cacheUsed: boolean }> => {
832
- const now = await this.dateRepository.now();
833
- const cache = await this.readCachedProjectIssues(projectId);
834
- const isFullFetch =
835
- cache === null ||
836
- now.getTime() - new Date(cache.lastFullFetchAt).getTime() >=
837
- FULL_ISSUE_FETCH_INTERVAL_MS;
838
-
839
- let project: Project;
840
- try {
841
- const freshProject = await this.projectRepository.getProject(projectId);
842
- if (!freshProject) {
843
- throw new Error(`Project not found. projectId: ${projectId}`);
844
- }
845
- project = freshProject;
846
- } catch (error) {
847
- if (!isFullFetch && cache !== null) {
848
- console.warn(
849
- `Failed to refresh project metadata, using cached. projectId: ${projectId}, error: ${String(error)}`,
850
- );
851
- project = cache.project;
852
- } else {
853
- throw error;
854
- }
855
- }
856
-
857
- const cacheStoriesByOptionId = new Map(
858
- cache?.project.story?.stories.map((s) => [s.id, s.name]) ?? [],
859
- );
860
- const freshStoryOptionIds = new Set(
861
- project.story?.stories.map((s) => s.id) ?? [],
862
- );
863
- const storyOptionsChanged =
864
- cache !== null &&
865
- ([...cacheStoriesByOptionId.entries()].some(
866
- ([id, name]) =>
867
- !freshStoryOptionIds.has(id) ||
868
- project.story?.stories.find((s) => s.id === id)?.name !== name,
869
- ) ??
870
- false);
871
- const effectiveIsFullFetch = isFullFetch || storyOptionsChanged;
872
-
873
- if (effectiveIsFullFetch) {
874
- const items =
875
- await this.graphqlProjectItemRepository.fetchProjectItems(projectId);
876
- const issues = items.map((item) => this.convertProjectItemToIssue(item));
877
- const nowIso = now.toISOString();
878
- await this.projectIssuesCacheRepository.write(projectId, {
879
- lastFetchedAt: nowIso,
880
- lastFullFetchAt: nowIso,
881
- project,
882
- issues,
883
- });
884
- this.lastIssuesFetchedAtByProjectId.set(projectId, nowIso);
885
- return { issues, project, cacheUsed: false };
886
- }
887
-
888
- const lastFetchedAt = new Date(cache.lastFetchedAt);
889
- const cutoff = new Date(
890
- lastFetchedAt.getTime() - INCREMENTAL_FETCH_SKEW_BUFFER_MS,
891
- );
892
- const lightItems =
893
- await this.graphqlProjectItemRepository.fetchProjectItemsLight(
894
- projectId,
895
- `updated:>=${this.toDateString(cutoff)}`,
896
- );
897
- const changedItemIds = lightItems
898
- .filter((item) => new Date(item.updatedAt).getTime() >= cutoff.getTime())
899
- .map((item) => item.id);
900
- const issuesByUrl = new Map<string, Issue>(
901
- cache.issues.map((issue) => [issue.url, issue]),
902
- );
903
- if (changedItemIds.length > 0) {
904
- const changedItems =
905
- await this.graphqlProjectItemRepository.fetchProjectItemsByIds(
906
- changedItemIds,
907
- );
908
- for (const item of changedItems) {
909
- const issue = this.convertProjectItemToIssue(item);
910
- issuesByUrl.set(issue.url, issue);
911
- }
912
- }
913
- const issues = Array.from(issuesByUrl.values());
914
- const nowIso = now.toISOString();
915
- await this.projectIssuesCacheRepository.write(projectId, {
916
- lastFetchedAt: nowIso,
917
- lastFullFetchAt: cache.lastFullFetchAt,
918
- project,
919
- issues,
920
- });
921
- this.lastIssuesFetchedAtByProjectId.set(projectId, nowIso);
922
- return { issues, project, cacheUsed: true };
923
- };
924
- createNewIssue = async (
925
- org: string,
926
- repo: string,
927
- title: string,
928
- body: string,
929
- assignees: string[],
930
- labels: string[],
931
- ): Promise<number> => {
932
- return await this.restIssueRepository.createNewIssue(
933
- org,
934
- repo,
935
- title,
936
- body,
937
- assignees,
938
- labels,
939
- );
940
- };
941
- searchIssue = async (query: {
942
- owner: string;
943
- repositoryName: string;
944
- type?: 'issue' | 'pr';
945
- state?: 'open' | 'closed' | 'all';
946
- title?: string;
947
- createdFrom?: string;
948
- assignee?: string;
949
- }): Promise<
950
- {
951
- url: string;
952
- title: string;
953
- number: string;
954
- }[]
955
- > => {
956
- return await this.apiV3IssueRepository.searchIssue(query);
957
- };
958
- updateIssue = async (issue: Issue): Promise<void> => {
959
- await this.restIssueRepository.updateIssue(issue);
960
- };
961
- updateIssueBody = async (
962
- issue: Pick<Issue, 'org' | 'repo' | 'number'>,
963
- body: string,
964
- ): Promise<void> => {
965
- await this.restIssueRepository.updateIssueBody(issue, body);
966
- };
967
- getIssueByUrl = async (url: string): Promise<Issue | null> => {
968
- const projectItem =
969
- await this.graphqlProjectItemRepository.fetchProjectItemByUrl(url);
970
- if (!projectItem) {
971
- return null;
972
- }
973
- return this.convertProjectItemToIssue(projectItem);
974
- };
975
- addIssueToProject = async (
976
- project: Project,
977
- issueUrl: string,
978
- ): Promise<void> => {
979
- await this.graphqlProjectItemRepository.addIssueToProject(
980
- project.id,
981
- issueUrl,
982
- );
983
- };
984
- setDependedIssueUrl = async (
985
- prUrl: string,
986
- project: Project,
987
- issueUrl: string,
988
- ): Promise<void> => {
989
- const dependedIssueUrlField = project.dependedIssueUrlSeparatedByComma;
990
- if (!dependedIssueUrlField) {
991
- return;
992
- }
993
- const existingProjectItem =
994
- await this.graphqlProjectItemRepository.fetchProjectItemByUrl(
995
- prUrl,
996
- project.id,
997
- );
998
- const existingValue = existingProjectItem?.customFields.find(
999
- (field) => field.name === dependedIssueUrlField.name,
1000
- )?.value;
1001
- if (existingValue) {
1002
- return;
1003
- }
1004
- const projectItemId =
1005
- existingProjectItem?.id ??
1006
- (await this.graphqlProjectItemRepository.addIssueToProject(
1007
- project.id,
1008
- prUrl,
1009
- ));
1010
- await this.graphqlProjectItemRepository.updateProjectTextField(
1011
- project.id,
1012
- dependedIssueUrlField.fieldId,
1013
- projectItemId,
1014
- issueUrl,
1015
- );
1016
- };
1017
-
1018
- setIssueAgentField = async (
1019
- issueUrl: string,
1020
- project: Project,
1021
- agentOptionId: string,
1022
- ): Promise<void> => {
1023
- if (!project.agent) {
1024
- return;
1025
- }
1026
- const existingProjectItem =
1027
- await this.graphqlProjectItemRepository.fetchProjectItemByUrl(
1028
- issueUrl,
1029
- project.id,
1030
- );
1031
- const projectItemId =
1032
- existingProjectItem?.id ??
1033
- (await this.graphqlProjectItemRepository.addIssueToProject(
1034
- project.id,
1035
- issueUrl,
1036
- ));
1037
- await this.graphqlProjectItemRepository.updateProjectField(
1038
- project.id,
1039
- project.agent.fieldId,
1040
- projectItemId,
1041
- { singleSelectOptionId: agentOptionId },
1042
- );
1043
- };
1044
-
1045
- updateNextActionDate = async (
1046
- issueUrl: string,
1047
- project: Project,
1048
- date: Date,
1049
- projectItemId?: string,
1050
- ): Promise<void> => {
1051
- if (!project.nextActionDate) {
1052
- return;
1053
- }
1054
- // When the caller already knows the project item id (e.g. the console,
1055
- // which receives it in the request body), use it directly and skip the
1056
- // GraphQL fetchProjectItemByUrl lookup. Fall back to the lookup only when
1057
- // no id was supplied, preserving the original behavior for other callers.
1058
- const itemId =
1059
- projectItemId ??
1060
- (
1061
- await this.graphqlProjectItemRepository.fetchProjectItemByUrl(
1062
- issueUrl,
1063
- project.id,
1064
- )
1065
- )?.id;
1066
- if (!itemId) {
1067
- return;
1068
- }
1069
- return this.graphqlProjectItemRepository.updateProjectField(
1070
- project.id,
1071
- project.nextActionDate.fieldId,
1072
- itemId,
1073
- { date: date.toISOString().split('T')[0] },
1074
- );
1075
- };
1076
- updateNextActionHour = async (
1077
- project: Project & {
1078
- nextActionHour: NonNullable<Project['nextActionHour']>;
1079
- },
1080
- issue: Issue,
1081
- hour: number,
1082
- ): Promise<void> => {
1083
- const option = project.nextActionHour.options.find(
1084
- (o) => o.name === String(hour),
1085
- );
1086
- const value = option
1087
- ? { singleSelectOptionId: option.id }
1088
- : { number: hour };
1089
- return this.graphqlProjectItemRepository.updateProjectField(
1090
- project.id,
1091
- project.nextActionHour.fieldId,
1092
- issue.itemId,
1093
- value,
1094
- );
1095
- };
1096
- updateStory = async (
1097
- project: Project & { story: NonNullable<Project['story']> },
1098
- issue: Issue,
1099
- storyOptionId: string,
1100
- ): Promise<void> => {
1101
- await this.graphqlProjectItemRepository.updateProjectField(
1102
- project.id,
1103
- project.story.fieldId,
1104
- issue.itemId,
1105
- { singleSelectOptionId: storyOptionId },
1106
- );
1107
- };
1108
- updateStoryOptionColor = async (
1109
- project: Project & { story: NonNullable<Project['story']> },
1110
- storyOptionId: string,
1111
- newColor: FieldOption['color'],
1112
- ): Promise<void> => {
1113
- const mutation = `mutation UpdateStoryOptionColor($fieldId: ID!, $options: [ProjectV2SingleSelectFieldOptionInput!]!) {
1114
- updateProjectV2Field(input: {
1115
- fieldId: $fieldId
1116
- singleSelectOptions: $options
1117
- }) {
1118
- projectV2Field {
1119
- ... on ProjectV2SingleSelectField {
1120
- options { id name color description }
1121
- }
1122
- }
1123
- }
1124
- }`;
1125
- const options = project.story.stories.map((opt) => ({
1126
- id: opt.id,
1127
- name: opt.name,
1128
- color: opt.id === storyOptionId ? newColor : opt.color,
1129
- description: opt.description,
1130
- }));
1131
- const response = await postGithubGraphqlJson<{
1132
- errors?: { message: string }[];
1133
- }>({
1134
- ghToken: this.ghToken,
1135
- query: mutation,
1136
- variables: { fieldId: project.story.fieldId, options },
1137
- });
1138
- if (response.errors && response.errors.length > 0) {
1139
- throw new Error(response.errors.map((e) => e.message).join('; '));
1140
- }
1141
- };
1142
- clearProjectField = async (
1143
- project: Project,
1144
- fieldId: string,
1145
- issue: Issue,
1146
- ): Promise<void> => {
1147
- await this.graphqlProjectItemRepository.clearProjectField(
1148
- project.id,
1149
- fieldId,
1150
- issue.itemId,
1151
- );
1152
- return;
1153
- };
1154
- createComment = async (issue: Issue, comment: string): Promise<void> => {
1155
- await this.restIssueRepository.createComment(issue.url, comment);
1156
- };
1157
- updateProjectTextField = async (
1158
- project: Project,
1159
- fieldId: string,
1160
- issue: Issue,
1161
- text: string,
1162
- ): Promise<void> => {
1163
- await this.graphqlProjectItemRepository.updateProjectTextField(
1164
- project.id,
1165
- fieldId,
1166
- issue.itemId,
1167
- text,
1168
- );
1169
- };
1170
-
1171
- updateLabels = (issue: Issue, labels: Issue['labels']): Promise<void> => {
1172
- return this.restIssueRepository.updateLabels(issue, labels);
1173
- };
1174
- removeLabel = (issue: Issue, label: string): Promise<void> => {
1175
- return this.restIssueRepository.removeLabel(issue, label);
1176
- };
1177
- getOrCreateLabel = (
1178
- org: string,
1179
- repo: string,
1180
- labelName: string,
1181
- ): Promise<void> => {
1182
- return this.restIssueRepository.getOrCreateLabel(org, repo, labelName);
1183
- };
1184
- updateAssigneeList = (
1185
- issue: Pick<Issue, 'org' | 'repo' | 'number'>,
1186
- assigneeList: Member['name'][],
1187
- ): Promise<void> => {
1188
- return this.restIssueRepository.updateAssigneeList(issue, assigneeList);
1189
- };
1190
- searchIssues = (query: string): Promise<SearchedIssue[]> => {
1191
- return this.restIssueRepository.searchIssues(query);
1192
- };
1193
- get = async (issueUrl: string, project: Project): Promise<Issue | null> => {
1194
- const projectItem =
1195
- await this.graphqlProjectItemRepository.fetchProjectItemByUrl(
1196
- issueUrl,
1197
- project.id,
1198
- );
1199
- if (!projectItem) {
1200
- return null;
1201
- }
1202
- return this.convertProjectItemToIssue(projectItem);
1203
- };
1204
- update = async (issue: Issue, _project: Project): Promise<void> => {
1205
- await this.updateIssue(issue);
1206
- };
1207
- private parseIssueUrl = (
1208
- issueUrl: string,
1209
- ): {
1210
- owner: string;
1211
- repo: string;
1212
- issueNumber: number;
1213
- isPr: boolean;
1214
- } => {
1215
- const urlMatch = issueUrl.match(
1216
- /github\.com\/([^/]+)\/([^/]+)\/(issues|pull)\/(\d+)/,
1217
- );
1218
- if (!urlMatch) {
1219
- throw new Error(`Invalid GitHub issue URL: ${issueUrl}`);
1220
- }
1221
- return {
1222
- owner: urlMatch[1],
1223
- repo: urlMatch[2],
1224
- issueNumber: parseInt(urlMatch[4], 10),
1225
- isPr: urlMatch[3] === 'pull',
1226
- };
1227
- };
1228
-
1229
- private computePrStatus = (
1230
- prUrl: string,
1231
- headRefName: string | undefined,
1232
- data: PrStatusComputationData,
1233
- ): RelatedPullRequest => {
1234
- const isConflicted = data.mergeable === 'CONFLICTING';
1235
- const hasStatusCheckRollup = data.ciContexts.length > 0;
1236
- const contexts = data.ciContexts;
1237
-
1238
- const requiredCheckNames = data.requiredCheckNames;
1239
- const seenContextNames = new Set<string>();
1240
- for (const ctx of contexts) {
1241
- if ('name' in ctx) {
1242
- seenContextNames.add(ctx.name);
1243
- }
1244
- if ('context' in ctx) {
1245
- seenContextNames.add(ctx.context);
1246
- }
1247
- }
1248
-
1249
- const missingRequiredCheckNames = requiredCheckNames.filter(
1250
- (name) => !seenContextNames.has(name),
1251
- );
1252
- const allRequiredChecksPassed = missingRequiredCheckNames.length === 0;
1253
-
1254
- const latestCheckRunByName = new Map<
1255
- string,
1256
- { conclusion: string | null; databaseId: number }
1257
- >();
1258
- for (const ctx of contexts) {
1259
- if (ctx.__typename === 'CheckRun') {
1260
- const existing = latestCheckRunByName.get(ctx.name);
1261
- if (!existing || ctx.databaseId > existing.databaseId) {
1262
- latestCheckRunByName.set(ctx.name, {
1263
- conclusion: ctx.conclusion,
1264
- databaseId: ctx.databaseId,
1265
- });
1266
- }
1267
- }
1268
- }
1269
- const failureConclusions = new Set([
1270
- 'FAILURE',
1271
- 'CANCELLED',
1272
- 'TIMED_OUT',
1273
- 'ACTION_REQUIRED',
1274
- 'STARTUP_FAILURE',
1275
- 'STALE',
1276
- ]);
1277
- const isCiStateSuccess = (() => {
1278
- if (!hasStatusCheckRollup) return false;
1279
- const latestRuns = [...latestCheckRunByName.values()];
1280
- const statusContexts = contexts.filter(
1281
- (
1282
- ctx,
1283
- ): ctx is {
1284
- __typename: 'StatusContext';
1285
- context: string;
1286
- state: string;
1287
- } => ctx.__typename === 'StatusContext',
1288
- );
1289
- const hasFailure =
1290
- latestRuns.some(
1291
- (r) => r.conclusion !== null && failureConclusions.has(r.conclusion),
1292
- ) ||
1293
- statusContexts.some(
1294
- (ctx) => ctx.state === 'FAILURE' || ctx.state === 'ERROR',
1295
- );
1296
- if (hasFailure) return false;
1297
- const hasPending =
1298
- latestRuns.some((r) => r.conclusion === null) ||
1299
- statusContexts.some((ctx) => ctx.state === 'PENDING');
1300
- return !hasPending;
1301
- })();
1302
- const isPassedAllCiJob = isCiStateSuccess && allRequiredChecksPassed;
1303
-
1304
- const reviewThreads = data.reviewThreads;
1305
- const isResolvedAllReviewComments =
1306
- reviewThreads.length === 0 ||
1307
- reviewThreads.every((thread) => thread.isResolved);
1308
-
1309
- return {
1310
- url: prUrl,
1311
- branchName: headRefName ?? null,
1312
- createdAt: new Date(0),
1313
- isDraft: data.isDraft === true,
1314
- isConflicted,
1315
- mergeable: data.mergeable ?? null,
1316
- isPassedAllCiJob,
1317
- isCiStateSuccess,
1318
- isResolvedAllReviewComments,
1319
- isBranchOutOfDate: false,
1320
- missingRequiredCheckNames,
1321
- };
1322
- };
1323
-
1324
- private prBodyContainsCrossRepoClosingKeyword = (
1325
- prBody: string | null,
1326
- issueUrl: string,
1327
- ): boolean => {
1328
- if (!prBody) return false;
1329
- const closingKeywords =
1330
- /(?:close|closes|closed|fix|fixes|fixed|resolve|resolves|resolved)\s+/gi;
1331
- const normalizedIssueUrl = issueUrl.replace(/\/+$/, '');
1332
- let match: RegExpExecArray | null;
1333
- while ((match = closingKeywords.exec(prBody)) !== null) {
1334
- const afterKeyword = prBody.slice(match.index + match[0].length);
1335
- const urlMatch = afterKeyword.match(/^https?:\/\/[^\s]+/);
1336
- if (!urlMatch) continue;
1337
- const candidateUrl = urlMatch[0].replace(/\/+$/, '');
1338
- if (candidateUrl.toLowerCase() === normalizedIssueUrl.toLowerCase())
1339
- return true;
1340
- }
1341
- return false;
1342
- };
1343
-
1344
- private readonly requiredCheckNamesCache = new Map<
1345
- string,
1346
- { fetchedAtMs: number; names: string[] }
1347
- >();
1348
-
1349
- private getRequiredCheckNames = async (
1350
- owner: string,
1351
- repo: string,
1352
- branch: string,
1353
- ): Promise<string[]> => {
1354
- const cacheKey = `${owner}/${repo}/${branch}`;
1355
- const nowMs = (await this.dateRepository.now()).getTime();
1356
- const cached = this.requiredCheckNamesCache.get(cacheKey);
1357
- if (cached && nowMs - cached.fetchedAtMs < REQUIRED_CHECKS_CACHE_TTL_MS) {
1358
- return cached.names;
1359
- }
1360
-
1361
- const ownerSegment = encodeURIComponent(owner);
1362
- const repoSegment = encodeURIComponent(repo);
1363
- const branchSegment = encodeURIComponent(branch);
1364
- const requiredCheckNamesSet = new Set<string>();
1365
-
1366
- const rulesResponse = await this.fetchWithRateLimitRetry(() =>
1367
- fetch(
1368
- `https://api.github.com/repos/${ownerSegment}/${repoSegment}/rules/branches/${branchSegment}?per_page=100`,
1369
- {
1370
- method: 'GET',
1371
- headers: {
1372
- Authorization: `Bearer ${this.ghToken}`,
1373
- Accept: 'application/vnd.github+json',
1374
- },
1375
- },
1376
- ),
1377
- );
1378
- if (rulesResponse.ok) {
1379
- const rulesBody: unknown = await rulesResponse.json();
1380
- if (!isBranchRulesResponse(rulesBody)) {
1381
- throw new Error(
1382
- `Unexpected response shape when fetching branch rules: ${owner}/${repo}/${branch}`,
1383
- );
1384
- }
1385
- for (const rule of rulesBody) {
1386
- if (rule.type !== 'required_status_checks') continue;
1387
- for (const check of rule.parameters?.required_status_checks || []) {
1388
- requiredCheckNamesSet.add(check.context);
1389
- }
1390
- }
1391
- } else if (rulesResponse.status === 403) {
1392
- const reason = await this.formatGitHubErrorWithStatus(rulesResponse);
1393
- console.warn(
1394
- `ApiV3CheerioRestIssueRepository: branch rules are not accessible for ${owner}/${repo}/${branch}, treating as no required checks. reason: ${reason}`,
1395
- );
1396
- } else if (rulesResponse.status !== 404) {
1397
- const reason = await this.formatGitHubErrorWithStatus(rulesResponse);
1398
- throw new Error(
1399
- `Failed to fetch branch rules for ${owner}/${repo}/${branch}: ${reason}`,
1400
- );
1401
- }
1402
-
1403
- const branchResponse = await this.fetchWithRateLimitRetry(() =>
1404
- fetch(
1405
- `https://api.github.com/repos/${ownerSegment}/${repoSegment}/branches/${branchSegment}`,
1406
- {
1407
- method: 'GET',
1408
- headers: {
1409
- Authorization: `Bearer ${this.ghToken}`,
1410
- Accept: 'application/vnd.github+json',
1411
- },
1412
- },
1413
- ),
1414
- );
1415
- if (branchResponse.ok) {
1416
- const branchBody: unknown = await branchResponse.json();
1417
- if (!isBranchDetailResponse(branchBody)) {
1418
- throw new Error(
1419
- `Unexpected response shape when fetching branch detail: ${owner}/${repo}/${branch}`,
1420
- );
1421
- }
1422
- for (const context of branchBody.protection?.required_status_checks
1423
- ?.contexts || []) {
1424
- requiredCheckNamesSet.add(context);
1425
- }
1426
- } else if (branchResponse.status === 403) {
1427
- const reason = await this.formatGitHubErrorWithStatus(branchResponse);
1428
- console.warn(
1429
- `ApiV3CheerioRestIssueRepository: branch detail (classic protection) is not accessible for ${owner}/${repo}/${branch}, treating as no required checks. reason: ${reason}`,
1430
- );
1431
- } else if (branchResponse.status !== 404) {
1432
- const reason = await this.formatGitHubErrorWithStatus(branchResponse);
1433
- throw new Error(
1434
- `Failed to fetch branch detail for ${owner}/${repo}/${branch}: ${reason}`,
1435
- );
1436
- }
1437
-
1438
- const names = Array.from(requiredCheckNamesSet);
1439
- this.requiredCheckNamesCache.set(cacheKey, {
1440
- fetchedAtMs: nowMs,
1441
- names,
1442
- });
1443
- return names;
1444
- };
1445
-
1446
- private getCheckRunsViaCheckSuitesFallback = async (
1447
- owner: string,
1448
- repo: string,
1449
- commitSha: string,
1450
- ): Promise<CiContextNode[]> => {
1451
- const ownerSegment = encodeURIComponent(owner);
1452
- const repoSegment = encodeURIComponent(repo);
1453
- const shaSegment = encodeURIComponent(commitSha);
1454
-
1455
- const checkSuitesResponse = await this.fetchWithRateLimitRetry(() =>
1456
- fetch(
1457
- `https://api.github.com/repos/${ownerSegment}/${repoSegment}/commits/${shaSegment}/check-suites`,
1458
- {
1459
- method: 'GET',
1460
- headers: {
1461
- Authorization: `Bearer ${this.ghToken}`,
1462
- Accept: 'application/vnd.github+json',
1463
- },
1464
- },
1465
- ),
1466
- );
1467
-
1468
- if (!checkSuitesResponse.ok) {
1469
- if (checkSuitesResponse.status === 404) {
1470
- console.warn(
1471
- `ApiV3CheerioRestIssueRepository: commits/${commitSha}/check-suites returned 404 for ${owner}/${repo}, treating as no check runs.`,
1472
- );
1473
- return [];
1474
- }
1475
- const reason =
1476
- await this.formatGitHubErrorWithStatus(checkSuitesResponse);
1477
- throw new Error(
1478
- `Failed to fetch check suites for ${owner}/${repo}@${commitSha}: ${reason}`,
1479
- );
1480
- }
1481
-
1482
- const checkSuitesBody: unknown = await checkSuitesResponse.json();
1483
- if (!isCheckSuitesResponse(checkSuitesBody)) {
1484
- throw new Error(
1485
- `Unexpected response shape when fetching check suites: ${owner}/${repo}@${commitSha}`,
1486
- );
1487
- }
1488
-
1489
- const contexts: CiContextNode[] = [];
1490
- const perPage = 100;
1491
- for (const suite of checkSuitesBody.check_suites) {
1492
- let page = 1;
1493
- let hasMore = true;
1494
- while (hasMore) {
1495
- const runsResponse = await this.fetchWithRateLimitRetry(() =>
1496
- fetch(
1497
- `https://api.github.com/repos/${ownerSegment}/${repoSegment}/check-suites/${suite.id}/check-runs?per_page=${perPage}&page=${page}`,
1498
- {
1499
- method: 'GET',
1500
- headers: {
1501
- Authorization: `Bearer ${this.ghToken}`,
1502
- Accept: 'application/vnd.github+json',
1503
- },
1504
- },
1505
- ),
1506
- );
1507
- if (!runsResponse.ok) {
1508
- const reason = await this.formatGitHubErrorWithStatus(runsResponse);
1509
- throw new Error(
1510
- `Failed to fetch check runs for suite ${suite.id} in ${owner}/${repo}: ${reason}`,
1511
- );
1512
- }
1513
- const runsBody: unknown = await runsResponse.json();
1514
- if (!isCheckRunsResponse(runsBody)) {
1515
- throw new Error(
1516
- `Unexpected response shape when fetching check runs for suite ${suite.id}: ${owner}/${repo}@${commitSha}`,
1517
- );
1518
- }
1519
- for (const checkRun of runsBody.check_runs) {
1520
- contexts.push({
1521
- __typename: 'CheckRun',
1522
- name: checkRun.name,
1523
- conclusion: checkRun.conclusion
1524
- ? checkRun.conclusion.toUpperCase()
1525
- : null,
1526
- databaseId: checkRun.id,
1527
- });
1528
- }
1529
- if (
1530
- runsBody.check_runs.length < perPage ||
1531
- page * perPage >= runsBody.total_count
1532
- ) {
1533
- hasMore = false;
1534
- } else {
1535
- page += 1;
1536
- }
1537
- }
1538
- }
1539
- return contexts;
1540
- };
1541
-
1542
- private getCommitCiContexts = async (
1543
- owner: string,
1544
- repo: string,
1545
- commitSha: string,
1546
- ): Promise<CiContextNode[]> => {
1547
- const ownerSegment = encodeURIComponent(owner);
1548
- const repoSegment = encodeURIComponent(repo);
1549
- const shaSegment = encodeURIComponent(commitSha);
1550
- const contexts: CiContextNode[] = [];
1551
-
1552
- const perPage = 100;
1553
- let page = 1;
1554
- let hasMore = true;
1555
- let usedCheckSuitesFallback = false;
1556
- while (hasMore) {
1557
- const checkRunsResponse = await this.fetchWithRateLimitRetry(() =>
1558
- fetch(
1559
- `https://api.github.com/repos/${ownerSegment}/${repoSegment}/commits/${shaSegment}/check-runs?per_page=${perPage}&page=${page}`,
1560
- {
1561
- method: 'GET',
1562
- headers: {
1563
- Authorization: `Bearer ${this.ghToken}`,
1564
- Accept: 'application/vnd.github+json',
1565
- },
1566
- },
1567
- ),
1568
- );
1569
- if (!checkRunsResponse.ok) {
1570
- if (checkRunsResponse.status === 404 && page === 1) {
1571
- const fallbackContexts =
1572
- await this.getCheckRunsViaCheckSuitesFallback(
1573
- owner,
1574
- repo,
1575
- commitSha,
1576
- );
1577
- contexts.push(...fallbackContexts);
1578
- usedCheckSuitesFallback = true;
1579
- break;
1580
- }
1581
- const reason =
1582
- await this.formatGitHubErrorWithStatus(checkRunsResponse);
1583
- throw new Error(
1584
- `Failed to fetch check runs for ${owner}/${repo}@${commitSha}: ${reason}`,
1585
- );
1586
- }
1587
- const checkRunsBody: unknown = await checkRunsResponse.json();
1588
- if (!isCheckRunsResponse(checkRunsBody)) {
1589
- throw new Error(
1590
- `Unexpected response shape when fetching check runs: ${owner}/${repo}@${commitSha}`,
1591
- );
1592
- }
1593
- for (const checkRun of checkRunsBody.check_runs) {
1594
- contexts.push({
1595
- __typename: 'CheckRun',
1596
- name: checkRun.name,
1597
- conclusion: checkRun.conclusion
1598
- ? checkRun.conclusion.toUpperCase()
1599
- : null,
1600
- databaseId: checkRun.id,
1601
- });
1602
- }
1603
- if (
1604
- checkRunsBody.check_runs.length < perPage ||
1605
- page * perPage >= checkRunsBody.total_count
1606
- ) {
1607
- hasMore = false;
1608
- } else {
1609
- page += 1;
1610
- }
1611
- }
1612
-
1613
- if (!usedCheckSuitesFallback) {
1614
- const combinedStatusResponse = await this.fetchWithRateLimitRetry(() =>
1615
- fetch(
1616
- `https://api.github.com/repos/${ownerSegment}/${repoSegment}/commits/${shaSegment}/status?per_page=100`,
1617
- {
1618
- method: 'GET',
1619
- headers: {
1620
- Authorization: `Bearer ${this.ghToken}`,
1621
- Accept: 'application/vnd.github+json',
1622
- },
1623
- },
1624
- ),
1625
- );
1626
- if (!combinedStatusResponse.ok) {
1627
- const reason = await this.formatGitHubErrorWithStatus(
1628
- combinedStatusResponse,
1629
- );
1630
- throw new Error(
1631
- `Failed to fetch combined status for ${owner}/${repo}@${commitSha}: ${reason}`,
1632
- );
1633
- }
1634
- const combinedStatusBody: unknown = await combinedStatusResponse.json();
1635
- if (!isCombinedStatusResponse(combinedStatusBody)) {
1636
- throw new Error(
1637
- `Unexpected response shape when fetching combined status: ${owner}/${repo}@${commitSha}`,
1638
- );
1639
- }
1640
- for (const status of combinedStatusBody.statuses) {
1641
- contexts.push({
1642
- __typename: 'StatusContext',
1643
- context: status.context,
1644
- state: status.state.toUpperCase(),
1645
- });
1646
- }
1647
- }
1648
-
1649
- return contexts;
1650
- };
1651
-
1652
- private fetchSlimPullRequest = async (
1653
- owner: string,
1654
- repo: string,
1655
- prNumber: number,
1656
- ): Promise<SlimPullRequest | null> => {
1657
- const query = `
1658
- query PullRequestSlimStatus($owner: String!, $repo: String!, $prNumber: Int!, $reviewThreadsAfter: String) {
1659
- repository(owner: $owner, name: $repo) {
1660
- pullRequest(number: $prNumber) {
1661
- url
1662
- state
1663
- isDraft
1664
- headRefName
1665
- baseRefName
1666
- mergeable
1667
- headRefOid
1668
- reviewThreads(first: 100, after: $reviewThreadsAfter) {
1669
- pageInfo {
1670
- endCursor
1671
- hasNextPage
1672
- }
1673
- nodes {
1674
- isResolved
1675
- }
1676
- }
1677
- }
1678
- }
1679
- }
1680
- `;
1681
-
1682
- let slimPullRequest: SlimPullRequest | null = null;
1683
- let reviewThreadsAfter: string | null = null;
1684
- let hasNextPage = true;
1685
-
1686
- while (hasNextPage) {
1687
- const response = await this.fetchWithRateLimitRetry(() =>
1688
- fetchGithubGraphql({
1689
- ghToken: this.ghToken,
1690
- query,
1691
- variables: { owner, repo, prNumber, reviewThreadsAfter },
1692
- }),
1693
- );
1694
-
1695
- if (!response.ok) {
1696
- throw new Error(
1697
- `Failed to fetch pull request from GitHub GraphQL API: HTTP ${response.status}`,
1698
- );
1699
- }
1700
-
1701
- const responseData: unknown = await response.json();
1702
- if (!isSlimPullRequestResponse(responseData)) {
1703
- throw new Error('Unexpected response shape when fetching pull request');
1704
- }
1705
-
1706
- if (responseData.errors && responseData.errors.length > 0) {
1707
- throw new Error(
1708
- `GraphQL errors: ${JSON.stringify(responseData.errors)}`,
1709
- );
1710
- }
1711
-
1712
- const pr = responseData.data?.repository?.pullRequest;
1713
- if (!pr) {
1714
- return null;
1715
- }
1716
-
1717
- if (!slimPullRequest) {
1718
- slimPullRequest = {
1719
- url: pr.url,
1720
- state: pr.state,
1721
- isDraft: pr.isDraft,
1722
- headRefName: pr.headRefName,
1723
- baseRefName: pr.baseRefName,
1724
- mergeable: pr.mergeable,
1725
- headRefOid: pr.headRefOid,
1726
- reviewThreads: [],
1727
- };
1728
- }
1729
- for (const thread of pr.reviewThreads?.nodes || []) {
1730
- slimPullRequest.reviewThreads.push({ isResolved: thread.isResolved });
1731
- }
1732
-
1733
- hasNextPage = pr.reviewThreads?.pageInfo.hasNextPage === true;
1734
- reviewThreadsAfter = pr.reviewThreads?.pageInfo.endCursor ?? null;
1735
- }
1736
-
1737
- return slimPullRequest;
1738
- };
1739
-
1740
- private buildRelatedPullRequestFromSlim = async (
1741
- owner: string,
1742
- repo: string,
1743
- slimPullRequest: SlimPullRequest,
1744
- ): Promise<RelatedPullRequest> => {
1745
- const requiredCheckNames = slimPullRequest.baseRefName
1746
- ? await this.getRequiredCheckNames(
1747
- owner,
1748
- repo,
1749
- slimPullRequest.baseRefName,
1750
- )
1751
- : [];
1752
- const ciContexts = slimPullRequest.headRefOid
1753
- ? await this.getCommitCiContexts(owner, repo, slimPullRequest.headRefOid)
1754
- : [];
1755
- return this.computePrStatus(
1756
- slimPullRequest.url,
1757
- slimPullRequest.headRefName,
1758
- {
1759
- isDraft: slimPullRequest.isDraft,
1760
- mergeable: slimPullRequest.mergeable,
1761
- requiredCheckNames,
1762
- ciContexts,
1763
- reviewThreads: slimPullRequest.reviewThreads,
1764
- },
1765
- );
1766
- };
1767
-
1768
- private resolveMergeabilityWithRetry = async (
1769
- owner: string,
1770
- repo: string,
1771
- prNumber: number,
1772
- ): Promise<{
1773
- mergeable: string | null;
1774
- mergeStateStatus: string | null;
1775
- } | null> => {
1776
- const query = `
1777
- query PullRequestMergeability($owner: String!, $repo: String!, $prNumber: Int!) {
1778
- repository(owner: $owner, name: $repo) {
1779
- pullRequest(number: $prNumber) {
1780
- mergeable
1781
- mergeStateStatus
1782
- }
1783
- }
1784
- }
1785
- `;
1786
-
1787
- const maxAttempts = 3;
1788
- const retryDelayMilliseconds = 1000;
1789
- let lastResult: {
1790
- mergeable: string | null;
1791
- mergeStateStatus: string | null;
1792
- } | null = null;
1793
-
1794
- for (let attempt = 0; attempt < maxAttempts; attempt++) {
1795
- if (attempt > 0) {
1796
- await this.sleep(retryDelayMilliseconds);
1797
- }
1798
-
1799
- const response = await this.fetchWithRateLimitRetry(() =>
1800
- fetchGithubGraphql({
1801
- ghToken: this.ghToken,
1802
- query,
1803
- variables: { owner, repo, prNumber },
1804
- }),
1805
- );
1806
-
1807
- if (!response.ok) {
1808
- throw new Error(
1809
- `Failed to fetch pull request mergeability from GitHub GraphQL API: HTTP ${response.status}`,
1810
- );
1811
- }
1812
-
1813
- const responseData: unknown = await response.json();
1814
- if (!isPullRequestMergeabilityResponse(responseData)) {
1815
- throw new Error(
1816
- `Unexpected response shape when fetching pull request mergeability: ${owner}/${repo}#${prNumber}`,
1817
- );
1818
- }
1819
-
1820
- if (responseData.errors && responseData.errors.length > 0) {
1821
- throw new Error(
1822
- `GraphQL errors: ${JSON.stringify(responseData.errors)}`,
1823
- );
1824
- }
1825
-
1826
- const pr = responseData.data?.repository?.pullRequest;
1827
- if (!pr) {
1828
- return null;
1829
- }
1830
-
1831
- lastResult = {
1832
- mergeable: pr.mergeable ?? null,
1833
- mergeStateStatus: pr.mergeStateStatus ?? null,
1834
- };
1835
-
1836
- if (lastResult.mergeable !== null && lastResult.mergeable !== 'UNKNOWN') {
1837
- return lastResult;
1838
- }
1839
- }
1840
-
1841
- return lastResult;
1842
- };
1843
-
1844
- findRelatedOpenPRs = async (
1845
- issueUrl: string,
1846
- ): Promise<RelatedPullRequest[]> => {
1847
- const { owner, repo, issueNumber, isPr } = this.parseIssueUrl(issueUrl);
1848
- if (isPr) {
1849
- throw new Error(
1850
- 'findRelatedOpenPRs only supports issue URLs, not pull request URLs',
1851
- );
1852
- }
1853
-
1854
- const query = `
1855
- query IssueRelatedOpenPullRequests($owner: String!, $repo: String!, $issueNumber: Int!, $after: String) {
1856
- repository(owner: $owner, name: $repo) {
1857
- issue(number: $issueNumber) {
1858
- timelineItems(first: 100, after: $after, itemTypes: [CROSS_REFERENCED_EVENT]) {
1859
- pageInfo {
1860
- endCursor
1861
- hasNextPage
1862
- }
1863
- nodes {
1864
- __typename
1865
- ... on CrossReferencedEvent {
1866
- willCloseTarget
1867
- source {
1868
- __typename
1869
- ... on PullRequest {
1870
- url
1871
- number
1872
- body
1873
- state
1874
- createdAt
1875
- isDraft
1876
- mergeable
1877
- headRefName
1878
- baseRefName
1879
- baseRef {
1880
- name
1881
- }
1882
- }
1883
- }
1884
- }
1885
- }
1886
- }
1887
- }
1888
- }
1889
- }
1890
- `;
1891
-
1892
- const relatedPRsMap: Map<string, RelatedPullRequest> = new Map();
1893
- let after: string | null = null;
1894
- let hasNextPage = true;
1895
-
1896
- while (hasNextPage) {
1897
- const response = await this.fetchWithRateLimitRetry(() =>
1898
- fetchGithubGraphql({
1899
- ghToken: this.ghToken,
1900
- query,
1901
- variables: { owner, repo, issueNumber, after },
1902
- }),
1903
- );
1904
-
1905
- if (!response.ok) {
1906
- await this.throwGitHubError(
1907
- 'Failed to fetch issue timeline from GitHub GraphQL API',
1908
- response,
1909
- );
1910
- }
1911
-
1912
- const responseData: unknown = await response.json();
1913
- if (!isIssueTimelineResponse(responseData)) {
1914
- throw new Error(
1915
- `Unexpected response shape when fetching issue timeline: ${issueUrl}`,
1916
- );
1917
- }
1918
-
1919
- const issueData = responseData.data?.repository?.issue;
1920
- if (!issueData) {
1921
- console.info(
1922
- `ApiV3CheerioRestIssueRepository: issue not found when fetching timeline, returning empty related PRs. issueUrl: ${issueUrl}`,
1923
- );
1924
- return [];
1925
- }
1926
-
1927
- for (const item of issueData.timelineItems.nodes) {
1928
- if (item.__typename !== 'CrossReferencedEvent') continue;
1929
- if (!item.source || item.source.__typename !== 'PullRequest') continue;
1930
- if (item.source.state !== 'OPEN') continue;
1931
- if (
1932
- !item.willCloseTarget &&
1933
- !this.prBodyContainsCrossRepoClosingKeyword(
1934
- item.source.body ?? null,
1935
- issueUrl,
1936
- )
1937
- )
1938
- continue;
1939
-
1940
- const pr = item.source;
1941
- const prUrl = pr.url || '';
1942
-
1943
- if (!prUrl) continue;
1944
-
1945
- const { owner: prOwner, repo: prRepo } = this.parseIssueUrl(prUrl);
1946
-
1947
- let isConflicted = pr.mergeable === 'CONFLICTING';
1948
- let mergeable: string | null = pr.mergeable ?? null;
1949
- if (
1950
- pr.number !== undefined &&
1951
- (pr.mergeable === undefined ||
1952
- pr.mergeable === null ||
1953
- pr.mergeable === 'UNKNOWN')
1954
- ) {
1955
- let resolved: {
1956
- mergeable: string | null;
1957
- mergeStateStatus: string | null;
1958
- } | null;
1959
- try {
1960
- resolved = await this.resolveMergeabilityWithRetry(
1961
- prOwner,
1962
- prRepo,
1963
- pr.number,
1964
- );
1965
- } catch (error) {
1966
- const errorMessage =
1967
- error instanceof Error ? error.message : String(error);
1968
- if (errorMessage.includes('NOT_FOUND')) {
1969
- console.info(
1970
- `ApiV3CheerioRestIssueRepository: pull request no longer exists, excluding it from related open PRs. prUrl: ${prUrl}`,
1971
- );
1972
- } else {
1973
- console.warn(
1974
- `ApiV3CheerioRestIssueRepository: resolveMergeabilityWithRetry failed, skipping PR for this cycle. prUrl: ${prUrl} error: ${errorMessage}`,
1975
- );
1976
- }
1977
- continue;
1978
- }
1979
- if (resolved !== null) {
1980
- mergeable = resolved.mergeable;
1981
- isConflicted =
1982
- resolved.mergeable === 'CONFLICTING' ||
1983
- resolved.mergeStateStatus === 'DIRTY';
1984
- }
1985
- }
1986
-
1987
- if (pr.number === undefined) continue;
1988
-
1989
- let prStatus: RelatedPullRequest;
1990
- try {
1991
- const slimPullRequest = await this.fetchSlimPullRequest(
1992
- prOwner,
1993
- prRepo,
1994
- pr.number,
1995
- );
1996
- if (!slimPullRequest || slimPullRequest.state !== 'OPEN') {
1997
- console.info(
1998
- `ApiV3CheerioRestIssueRepository: pull request is no longer open, excluding it from related open PRs. prUrl: ${prUrl}`,
1999
- );
2000
- continue;
2001
- }
2002
- const baseRefName =
2003
- slimPullRequest.baseRefName ?? pr.baseRefName ?? pr.baseRef?.name;
2004
- prStatus = await this.buildRelatedPullRequestFromSlim(
2005
- prOwner,
2006
- prRepo,
2007
- {
2008
- ...slimPullRequest,
2009
- url: slimPullRequest.url || prUrl,
2010
- headRefName: slimPullRequest.headRefName ?? pr.headRefName,
2011
- baseRefName,
2012
- },
2013
- );
2014
- } catch (error) {
2015
- const errorMessage =
2016
- error instanceof Error ? error.message : String(error);
2017
- console.warn(
2018
- `ApiV3CheerioRestIssueRepository: fetching pull request status failed, skipping PR for this cycle. prUrl: ${prUrl} error: ${errorMessage}`,
2019
- );
2020
- continue;
2021
- }
2022
-
2023
- relatedPRsMap.set(prUrl, {
2024
- ...prStatus,
2025
- isConflicted,
2026
- mergeable,
2027
- createdAt: pr.createdAt ? new Date(pr.createdAt) : new Date(0),
2028
- });
2029
- }
2030
-
2031
- hasNextPage = issueData.timelineItems.pageInfo.hasNextPage;
2032
- after = issueData.timelineItems.pageInfo.endCursor;
2033
- }
2034
-
2035
- return Array.from(relatedPRsMap.values());
2036
- };
2037
-
2038
- getAllOpened = async (project: Project): Promise<Issue[]> => {
2039
- const { issues } = await this.getAllIssues(project.id);
2040
- return issues.filter((issue) => !issue.isClosed);
2041
- };
2042
-
2043
- getStoryObjectMap = async (project: Project): Promise<StoryObjectMap> => {
2044
- const { issues } = await this.getAllIssues(project.id);
2045
- const storyObjectMap: StoryObjectMap = new Map();
2046
- const targetStories = project.story?.stories || [];
2047
- for (const story of targetStories) {
2048
- const storyIssue = issues.find((issue) =>
2049
- story.name.startsWith(issue.title),
2050
- );
2051
- storyObjectMap.set(story.name, {
2052
- story,
2053
- storyIssue: storyIssue || null,
2054
- issues: [],
2055
- });
2056
- for (const issue of issues) {
2057
- if (issue.story !== story.name) continue;
2058
- storyObjectMap.get(story.name)?.issues.push(issue);
2059
- }
2060
- }
2061
- return storyObjectMap;
2062
- };
2063
-
2064
- getOpenPullRequest = async (
2065
- prUrl: string,
2066
- ): Promise<RelatedPullRequest | null> => {
2067
- const parsedUrl = this.parseIssueUrl(prUrl);
2068
- if (!parsedUrl.isPr) {
2069
- return null;
2070
- }
2071
- const { owner, repo, issueNumber: prNumber } = parsedUrl;
2072
-
2073
- const slimPullRequest = await this.fetchSlimPullRequest(
2074
- owner,
2075
- repo,
2076
- prNumber,
2077
- );
2078
- if (!slimPullRequest || slimPullRequest.state !== 'OPEN') {
2079
- return null;
2080
- }
2081
-
2082
- return this.buildRelatedPullRequestFromSlim(owner, repo, slimPullRequest);
2083
- };
2084
-
2085
- private fetchRestPullRequestCiStatus = async (
2086
- owner: string,
2087
- repo: string,
2088
- prNumber: number,
2089
- prUrl: string,
2090
- ): Promise<RestPullRequestCiStatusResponse | null> => {
2091
- const maxAttempts = 3;
2092
- const retryDelayMilliseconds = 1000;
2093
- let lastPullRequest: RestPullRequestCiStatusResponse | null = null;
2094
- for (let attempt = 0; attempt < maxAttempts; attempt++) {
2095
- if (attempt > 0) {
2096
- await this.sleep(retryDelayMilliseconds);
2097
- }
2098
- const response = await this.fetchWithRateLimitRetry(() =>
2099
- fetch(
2100
- `https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/pulls/${prNumber}`,
2101
- {
2102
- method: 'GET',
2103
- headers: {
2104
- Authorization: `Bearer ${this.ghToken}`,
2105
- Accept: 'application/vnd.github+json',
2106
- },
2107
- },
2108
- ),
2109
- );
2110
- if (response.status === 404) {
2111
- return null;
2112
- }
2113
- if (!response.ok) {
2114
- await this.throwGitHubError(
2115
- `Failed to fetch pull request status for ${prUrl}`,
2116
- response,
2117
- );
2118
- }
2119
- const body: unknown = await response.json();
2120
- if (!isRestPullRequestCiStatusResponse(body)) {
2121
- throw new Error(
2122
- `Unexpected response shape when fetching pull request status for ${prUrl}`,
2123
- );
2124
- }
2125
- lastPullRequest = body;
2126
- if (body.state !== 'open' || body.mergeable !== null) {
2127
- return body;
2128
- }
2129
- }
2130
- return lastPullRequest;
2131
- };
2132
-
2133
- getOpenPullRequestCiStatus = async (
2134
- prUrl: string,
2135
- ): Promise<OpenPullRequestCiStatus | null> => {
2136
- const parsedUrl = this.parseIssueUrl(prUrl);
2137
- if (!parsedUrl.isPr) {
2138
- return null;
2139
- }
2140
- const { owner, repo, issueNumber: prNumber } = parsedUrl;
2141
-
2142
- const pullRequest = await this.fetchRestPullRequestCiStatus(
2143
- owner,
2144
- repo,
2145
- prNumber,
2146
- prUrl,
2147
- );
2148
- if (!pullRequest || pullRequest.state !== 'open') {
2149
- return null;
2150
- }
2151
-
2152
- const requiredCheckNames = await this.getRequiredCheckNames(
2153
- owner,
2154
- repo,
2155
- pullRequest.base.ref,
2156
- );
2157
- const ciContexts = await this.getCommitCiContexts(
2158
- owner,
2159
- repo,
2160
- pullRequest.head.sha,
2161
- );
2162
- const status = this.computePrStatus(
2163
- pullRequest.html_url,
2164
- pullRequest.head.ref,
2165
- {
2166
- isDraft: pullRequest.draft,
2167
- mergeable: graphqlMergeableFromRestMergeable(pullRequest.mergeable),
2168
- requiredCheckNames,
2169
- ciContexts,
2170
- reviewThreads: [],
2171
- },
2172
- );
2173
- return {
2174
- url: status.url,
2175
- isConflicted: status.isConflicted,
2176
- mergeable: status.mergeable,
2177
- isPassedAllCiJob: status.isPassedAllCiJob,
2178
- isCiStateSuccess: status.isCiStateSuccess,
2179
- isBranchOutOfDate: status.isBranchOutOfDate,
2180
- missingRequiredCheckNames: status.missingRequiredCheckNames,
2181
- };
2182
- };
2183
-
2184
- // Resolves many pull requests with one GraphQL query per hundred instead of
2185
- // one query per pull request. A URL this cannot settle is left out of the
2186
- // returned map rather than mapped to null, so the caller falls back to
2187
- // getOpenPullRequest for it and an unknown state is never mistaken for an
2188
- // absent pull request.
2189
- getOpenPullRequests = async (
2190
- prUrls: string[],
2191
- ): Promise<Map<string, RelatedPullRequest | null>> => {
2192
- const resolved = new Map<string, RelatedPullRequest | null>();
2193
- const parsedByUrl = new Map<
2194
- string,
2195
- { owner: string; repo: string; prNumber: number }
2196
- >();
2197
- for (const prUrl of Array.from(new Set(prUrls))) {
2198
- const parsedUrl = this.parseIssueUrl(prUrl);
2199
- if (!parsedUrl.isPr) {
2200
- resolved.set(prUrl, null);
2201
- continue;
2202
- }
2203
- parsedByUrl.set(prUrl, {
2204
- owner: parsedUrl.owner,
2205
- repo: parsedUrl.repo,
2206
- prNumber: parsedUrl.issueNumber,
2207
- });
2208
- }
2209
-
2210
- const urlsToFetch = Array.from(parsedByUrl.keys());
2211
- for (
2212
- let start = 0;
2213
- start < urlsToFetch.length;
2214
- start += SLIM_PULL_REQUEST_BATCH_SIZE
2215
- ) {
2216
- const batchUrls = urlsToFetch.slice(
2217
- start,
2218
- start + SLIM_PULL_REQUEST_BATCH_SIZE,
2219
- );
2220
- let slimByUrl: Map<string, SlimPullRequest | null>;
2221
- try {
2222
- slimByUrl = await this.fetchSlimPullRequestsInOneQuery(
2223
- batchUrls.map((prUrl) => ({
2224
- prUrl,
2225
- ...this.requireParsedPullRequest(parsedByUrl, prUrl),
2226
- })),
2227
- );
2228
- } catch (error) {
2229
- console.warn(
2230
- `ApiV3CheerioRestIssueRepository: batched pull request status query failed, leaving ${batchUrls.length} pull request(s) to per-request resolution. error: ${error instanceof Error ? error.message : String(error)}`,
2231
- );
2232
- continue;
2233
- }
2234
- for (const prUrl of batchUrls) {
2235
- if (!slimByUrl.has(prUrl)) {
2236
- continue;
2237
- }
2238
- const slimPullRequest = slimByUrl.get(prUrl) ?? null;
2239
- if (!slimPullRequest || slimPullRequest.state !== 'OPEN') {
2240
- resolved.set(prUrl, null);
2241
- continue;
2242
- }
2243
- const parsed = this.requireParsedPullRequest(parsedByUrl, prUrl);
2244
- try {
2245
- resolved.set(
2246
- prUrl,
2247
- await this.buildRelatedPullRequestFromSlim(
2248
- parsed.owner,
2249
- parsed.repo,
2250
- slimPullRequest,
2251
- ),
2252
- );
2253
- } catch (error) {
2254
- console.warn(
2255
- `ApiV3CheerioRestIssueRepository: building the pull request status failed, leaving it to per-request resolution. prUrl: ${prUrl} error: ${error instanceof Error ? error.message : String(error)}`,
2256
- );
2257
- }
2258
- }
2259
- }
2260
- return resolved;
2261
- };
2262
-
2263
- private requireParsedPullRequest = (
2264
- parsedByUrl: Map<string, { owner: string; repo: string; prNumber: number }>,
2265
- prUrl: string,
2266
- ): { owner: string; repo: string; prNumber: number } => {
2267
- const parsed = parsedByUrl.get(prUrl);
2268
- if (!parsed) {
2269
- throw new Error(`Pull request URL was not parsed: ${prUrl}`);
2270
- }
2271
- return parsed;
2272
- };
2273
-
2274
- // A pull request whose review threads do not fit the first page is omitted
2275
- // from the returned map, so the caller resolves it through the paginating
2276
- // single-pull-request path and the resolved-thread state stays complete.
2277
- private fetchSlimPullRequestsInOneQuery = async (
2278
- references: {
2279
- prUrl: string;
2280
- owner: string;
2281
- repo: string;
2282
- prNumber: number;
2283
- }[],
2284
- ): Promise<Map<string, SlimPullRequest | null>> => {
2285
- const aliasOf = (index: number): string => `pullRequest${index}`;
2286
- const variableDeclarations = references
2287
- .map(
2288
- (_, index) =>
2289
- `$owner${index}: String!, $repo${index}: String!, $prNumber${index}: Int!`,
2290
- )
2291
- .join(', ');
2292
- const selections = references
2293
- .map(
2294
- (
2295
- _,
2296
- index,
2297
- ) => ` ${aliasOf(index)}: repository(owner: $owner${index}, name: $repo${index}) {
2298
- pullRequest(number: $prNumber${index}) {
2299
- url
2300
- state
2301
- isDraft
2302
- headRefName
2303
- baseRefName
2304
- mergeable
2305
- headRefOid
2306
- reviewThreads(first: ${SLIM_PULL_REQUEST_REVIEW_THREADS_PAGE_SIZE}) {
2307
- pageInfo {
2308
- endCursor
2309
- hasNextPage
2310
- }
2311
- nodes {
2312
- isResolved
2313
- }
2314
- }
2315
- }
2316
- }`,
2317
- )
2318
- .join('\n');
2319
- const query = `query PullRequestSlimStatusBatch(${variableDeclarations}) {\n${selections}\n}`;
2320
- const variables: Record<string, string | number> = {};
2321
- references.forEach((reference, index) => {
2322
- variables[`owner${index}`] = reference.owner;
2323
- variables[`repo${index}`] = reference.repo;
2324
- variables[`prNumber${index}`] = reference.prNumber;
2325
- });
2326
-
2327
- const response = await this.fetchWithRateLimitRetry(() =>
2328
- fetchGithubGraphql({ ghToken: this.ghToken, query, variables }),
2329
- );
2330
- if (!response.ok) {
2331
- throw new Error(
2332
- `Failed to fetch pull requests from GitHub GraphQL API: HTTP ${response.status}`,
2333
- );
2334
- }
2335
- const responseData: unknown = await response.json();
2336
- if (!isSlimPullRequestBatchResponse(responseData)) {
2337
- throw new Error('Unexpected response shape when fetching pull requests');
2338
- }
2339
- const errors = responseData.errors || [];
2340
- const notFoundAliases = new Set(
2341
- errors
2342
- .filter((error) => error.type === 'NOT_FOUND')
2343
- .map((error) => String(error.path?.[0] ?? '')),
2344
- );
2345
- const otherErrorAliases = new Set(
2346
- errors
2347
- .filter((error) => error.type !== 'NOT_FOUND')
2348
- .map((error) => String(error.path?.[0] ?? '')),
2349
- );
2350
- if (!responseData.data) {
2351
- throw new Error(`GraphQL errors: ${JSON.stringify(errors)}`);
2352
- }
2353
- const unattributedError = errors.some(
2354
- (error) => error.type !== 'NOT_FOUND' && !error.path,
2355
- );
2356
- if (unattributedError) {
2357
- throw new Error(`GraphQL errors: ${JSON.stringify(errors)}`);
2358
- }
2359
-
2360
- const slimByUrl = new Map<string, SlimPullRequest | null>();
2361
- references.forEach((reference, index) => {
2362
- const alias = aliasOf(index);
2363
- if (otherErrorAliases.has(alias)) {
2364
- return;
2365
- }
2366
- const node = responseData.data?.[alias]?.pullRequest ?? null;
2367
- if (!node) {
2368
- if (notFoundAliases.has(alias) || !errors.length) {
2369
- slimByUrl.set(reference.prUrl, null);
2370
- }
2371
- return;
2372
- }
2373
- if (node.reviewThreads?.pageInfo.hasNextPage === true) {
2374
- return;
2375
- }
2376
- slimByUrl.set(reference.prUrl, {
2377
- url: node.url,
2378
- state: node.state,
2379
- isDraft: node.isDraft,
2380
- headRefName: node.headRefName,
2381
- baseRefName: node.baseRefName,
2382
- mergeable: node.mergeable,
2383
- headRefOid: node.headRefOid,
2384
- reviewThreads: (node.reviewThreads?.nodes || []).map((thread) => ({
2385
- isResolved: thread.isResolved,
2386
- })),
2387
- });
2388
- });
2389
- return slimByUrl;
2390
- };
2391
-
2392
- closePullRequest = async (prUrl: string): Promise<void> => {
2393
- const { owner, repo, issueNumber: prNumber } = this.parseIssueUrl(prUrl);
2394
- const response = await this.fetchWithRateLimitRetry(() =>
2395
- fetch(
2396
- `https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/pulls/${prNumber}`,
2397
- {
2398
- method: 'PATCH',
2399
- headers: {
2400
- Authorization: `Bearer ${this.ghToken}`,
2401
- 'Content-Type': 'application/json',
2402
- },
2403
- body: JSON.stringify({ state: 'closed' }),
2404
- },
2405
- ),
2406
- );
2407
- if (!response.ok) {
2408
- const reason = await this.formatGitHubErrorWithStatus(response);
2409
- throw new Error(`Failed to close PR ${prUrl}: ${reason}`);
2410
- }
2411
- };
2412
-
2413
- closeIssueByUrl = async (
2414
- issueUrl: string,
2415
- stateReason: 'completed' | 'not_planned',
2416
- ): Promise<void> => {
2417
- const { owner, repo, issueNumber } = this.parseIssueUrl(issueUrl);
2418
- const ownerSegment = encodeURIComponent(owner);
2419
- const repoSegment = encodeURIComponent(repo);
2420
- const response = await this.fetchWithRateLimitRetry(() =>
2421
- fetch(
2422
- `https://api.github.com/repos/${ownerSegment}/${repoSegment}/issues/${issueNumber}`,
2423
- {
2424
- method: 'PATCH',
2425
- headers: {
2426
- Authorization: `Bearer ${this.ghToken}`,
2427
- 'Content-Type': 'application/json',
2428
- },
2429
- body: JSON.stringify({ state: 'closed', state_reason: stateReason }),
2430
- },
2431
- ),
2432
- );
2433
- if (!response.ok) {
2434
- const reason = await this.formatGitHubErrorWithStatus(response);
2435
- throw new Error(`Failed to close issue ${issueUrl}: ${reason}`);
2436
- }
2437
- };
2438
-
2439
- getPullRequestChangedFilePaths = async (prUrl: string): Promise<string[]> => {
2440
- const { owner, repo, issueNumber: prNumber } = this.parseIssueUrl(prUrl);
2441
- const perPage = 100;
2442
- const collectedPaths: string[] = [];
2443
- let page = 1;
2444
- let hasMore = true;
2445
- while (hasMore) {
2446
- const response = await this.fetchWithRateLimitRetry(() =>
2447
- fetch(
2448
- `https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/pulls/${prNumber}/files?per_page=${perPage}&page=${page}`,
2449
- {
2450
- method: 'GET',
2451
- headers: {
2452
- Authorization: `Bearer ${this.ghToken}`,
2453
- Accept: 'application/vnd.github+json',
2454
- },
2455
- },
2456
- ),
2457
- );
2458
- if (!response.ok) {
2459
- const reason = await this.formatGitHubErrorWithStatus(response);
2460
- throw new Error(
2461
- `Failed to fetch changed files for PR ${prUrl}: ${reason}`,
2462
- );
2463
- }
2464
- const body: unknown = await response.json();
2465
- if (!isPullRequestFilesResponse(body)) {
2466
- throw new Error(
2467
- `Unexpected response shape when fetching changed files for PR ${prUrl}`,
2468
- );
2469
- }
2470
- for (const file of body) {
2471
- collectedPaths.push(file.filename);
2472
- }
2473
- if (body.length < perPage) {
2474
- hasMore = false;
2475
- } else {
2476
- page += 1;
2477
- }
2478
- }
2479
- return collectedPaths;
2480
- };
2481
-
2482
- getAuthenticatedUserLogin = async (): Promise<string> => {
2483
- const response = await this.fetchWithRateLimitRetry(() =>
2484
- fetch('https://api.github.com/user', {
2485
- method: 'GET',
2486
- headers: {
2487
- Authorization: `Bearer ${this.ghToken}`,
2488
- Accept: 'application/vnd.github+json',
2489
- },
2490
- }),
2491
- );
2492
- if (!response.ok) {
2493
- const reason = await this.formatGitHubErrorWithStatus(response);
2494
- throw new Error(`Failed to fetch authenticated user: ${reason}`);
2495
- }
2496
- const body: unknown = await response.json();
2497
- if (!isAuthenticatedUserResponse(body)) {
2498
- throw new Error(
2499
- 'Unexpected response shape when fetching authenticated user',
2500
- );
2501
- }
2502
- return body.login;
2503
- };
2504
-
2505
- approvePullRequest = async (prUrl: string): Promise<void> => {
2506
- const { owner, repo, issueNumber: prNumber } = this.parseIssueUrl(prUrl);
2507
- const response = await this.fetchWithRateLimitRetry(() =>
2508
- fetch(
2509
- `https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/pulls/${prNumber}/reviews`,
2510
- {
2511
- method: 'POST',
2512
- headers: {
2513
- Authorization: `Bearer ${this.ghToken}`,
2514
- 'Content-Type': 'application/json',
2515
- Accept: 'application/vnd.github+json',
2516
- },
2517
- body: JSON.stringify({ event: 'APPROVE' }),
2518
- },
2519
- ),
2520
- );
2521
- if (!response.ok) {
2522
- const reason = await this.formatGitHubErrorWithStatus(response);
2523
- throw new Error(`Failed to approve PR ${prUrl}: ${reason}`);
2524
- }
2525
- };
2526
-
2527
- mergePullRequest = async (prUrl: string): Promise<void> => {
2528
- const { owner, repo, issueNumber: prNumber } = this.parseIssueUrl(prUrl);
2529
- const mergeUrl = `https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/pulls/${prNumber}/merge`;
2530
- const headers = {
2531
- Authorization: `Bearer ${this.ghToken}`,
2532
- 'Content-Type': 'application/json',
2533
- Accept: 'application/vnd.github+json',
2534
- };
2535
- const mergeWith = (mergeMethod?: 'squash' | 'rebase') =>
2536
- this.fetchWithRateLimitRetry(() =>
2537
- fetch(mergeUrl, {
2538
- method: 'PUT',
2539
- headers,
2540
- body: JSON.stringify(
2541
- mergeMethod ? { merge_method: mergeMethod } : {},
2542
- ),
2543
- }),
2544
- );
2545
- const response = await mergeWith();
2546
- if (response.ok) {
2547
- return;
2548
- }
2549
- if (response.status === 405) {
2550
- const fallbackMethod = await this.resolveAllowedMergeMethod(owner, repo);
2551
- if (fallbackMethod !== null) {
2552
- const retryResponse = await mergeWith(fallbackMethod);
2553
- if (retryResponse.ok) {
2554
- return;
2555
- }
2556
- const retryReason =
2557
- await this.formatGitHubErrorWithStatus(retryResponse);
2558
- throw new Error(`Failed to merge PR ${prUrl}: ${retryReason}`);
2559
- }
2560
- }
2561
- const reason = await this.formatGitHubErrorWithStatus(response);
2562
- throw new Error(`Failed to merge PR ${prUrl}: ${reason}`);
2563
- };
2564
-
2565
- private resolveAllowedMergeMethod = async (
2566
- owner: string,
2567
- repo: string,
2568
- ): Promise<'squash' | 'rebase' | null> => {
2569
- const response = await this.fetchWithRateLimitRetry(() =>
2570
- fetch(
2571
- `https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}`,
2572
- {
2573
- headers: {
2574
- Authorization: `Bearer ${this.ghToken}`,
2575
- Accept: 'application/vnd.github+json',
2576
- },
2577
- },
2578
- ),
2579
- );
2580
- if (!response.ok) {
2581
- return null;
2582
- }
2583
- const data: unknown = await response.json();
2584
- if (!isRepoMergeSettings(data)) {
2585
- return null;
2586
- }
2587
- if (data.allow_squash_merge === true) {
2588
- return 'squash';
2589
- }
2590
- if (data.allow_rebase_merge === true) {
2591
- return 'rebase';
2592
- }
2593
- return null;
2594
- };
2595
-
2596
- requestChangesWithInlineComment = async (
2597
- prUrl: string,
2598
- changedFilePath: string | null,
2599
- commentBody: string,
2600
- inlineCommentLocation: PullRequestReviewInlineLocation | null = null,
2601
- ): Promise<void> => {
2602
- const { owner, repo, issueNumber: prNumber } = this.parseIssueUrl(prUrl);
2603
- if (changedFilePath === null) {
2604
- await this.createCommentByUrl(prUrl, commentBody);
2605
- return;
2606
- }
2607
- const inlineComment =
2608
- inlineCommentLocation === null
2609
- ? { path: changedFilePath, position: 1, body: commentBody }
2610
- : {
2611
- path: changedFilePath,
2612
- line: inlineCommentLocation.line,
2613
- side: inlineCommentLocation.side,
2614
- body: commentBody,
2615
- };
2616
- const reviewBody: Record<string, unknown> = {
2617
- event: 'REQUEST_CHANGES',
2618
- body: commentBody,
2619
- comments: [inlineComment],
2620
- };
2621
- const response = await this.fetchWithRateLimitRetry(() =>
2622
- fetch(
2623
- `https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/pulls/${prNumber}/reviews`,
2624
- {
2625
- method: 'POST',
2626
- headers: {
2627
- Authorization: `Bearer ${this.ghToken}`,
2628
- 'Content-Type': 'application/json',
2629
- Accept: 'application/vnd.github+json',
2630
- },
2631
- body: JSON.stringify(reviewBody),
2632
- },
2633
- ),
2634
- );
2635
- if (!response.ok) {
2636
- const reason = await this.formatGitHubErrorWithStatus(response);
2637
- if (
2638
- response.status === 422 &&
2639
- reason.includes(SELF_AUTHORED_REVIEW_REFUSAL)
2640
- ) {
2641
- await this.recordRequestedChangesWithoutReview(
2642
- prUrl,
2643
- changedFilePath,
2644
- commentBody,
2645
- inlineCommentLocation,
2646
- );
2647
- return;
2648
- }
2649
- throw new Error(`Failed to request changes on PR ${prUrl}: ${reason}`);
2650
- }
2651
- };
2652
-
2653
- private recordRequestedChangesWithoutReview = async (
2654
- prUrl: string,
2655
- changedFilePath: string,
2656
- commentBody: string,
2657
- inlineCommentLocation: PullRequestReviewInlineLocation | null,
2658
- ): Promise<void> => {
2659
- if (inlineCommentLocation === null) {
2660
- await this.createCommentByUrl(prUrl, commentBody);
2661
- return;
2662
- }
2663
- await this.createPullRequestReviewComment(
2664
- prUrl,
2665
- changedFilePath,
2666
- inlineCommentLocation.line,
2667
- inlineCommentLocation.side,
2668
- commentBody,
2669
- );
2670
- };
2671
-
2672
- private fetchPullRequestHeadSha = async (
2673
- owner: string,
2674
- repo: string,
2675
- prNumber: number,
2676
- prUrl: string,
2677
- ): Promise<string> => {
2678
- const ownerSegment = encodeURIComponent(owner);
2679
- const repoSegment = encodeURIComponent(repo);
2680
- const response = await this.fetchWithRateLimitRetry(() =>
2681
- fetch(
2682
- `https://api.github.com/repos/${ownerSegment}/${repoSegment}/pulls/${prNumber}`,
2683
- {
2684
- method: 'GET',
2685
- headers: {
2686
- Authorization: `Bearer ${this.ghToken}`,
2687
- Accept: 'application/vnd.github+json',
2688
- },
2689
- },
2690
- ),
2691
- );
2692
- if (!response.ok) {
2693
- const reason = await this.formatGitHubErrorWithStatus(response);
2694
- throw new Error(`Failed to fetch head commit for PR ${prUrl}: ${reason}`);
2695
- }
2696
- const body: unknown = await response.json();
2697
- if (
2698
- !isRecord(body) ||
2699
- !isRecord(body.head) ||
2700
- typeof body.head.sha !== 'string'
2701
- ) {
2702
- throw new Error(
2703
- `Unexpected response shape when fetching head commit for PR ${prUrl}`,
2704
- );
2705
- }
2706
- return body.head.sha;
2707
- };
2708
-
2709
- createPullRequestReviewComment = async (
2710
- prUrl: string,
2711
- path: string,
2712
- line: number,
2713
- side: PullRequestReviewCommentSide,
2714
- commentBody: string,
2715
- ): Promise<void> => {
2716
- const { owner, repo, issueNumber: prNumber } = this.parseIssueUrl(prUrl);
2717
- const commitId = await this.fetchPullRequestHeadSha(
2718
- owner,
2719
- repo,
2720
- prNumber,
2721
- prUrl,
2722
- );
2723
- const ownerSegment = encodeURIComponent(owner);
2724
- const repoSegment = encodeURIComponent(repo);
2725
- const response = await this.fetchWithRateLimitRetry(() =>
2726
- fetch(
2727
- `https://api.github.com/repos/${ownerSegment}/${repoSegment}/pulls/${prNumber}/comments`,
2728
- {
2729
- method: 'POST',
2730
- headers: {
2731
- Authorization: `Bearer ${this.ghToken}`,
2732
- 'Content-Type': 'application/json',
2733
- Accept: 'application/vnd.github+json',
2734
- },
2735
- body: JSON.stringify({
2736
- body: commentBody,
2737
- commit_id: commitId,
2738
- path,
2739
- line,
2740
- side,
2741
- }),
2742
- },
2743
- ),
2744
- );
2745
- if (!response.ok) {
2746
- const reason = await this.formatGitHubErrorWithStatus(response);
2747
- throw new Error(
2748
- `Failed to create review comment on PR ${prUrl}: ${reason}`,
2749
- );
2750
- }
2751
- };
2752
-
2753
- private readGitHubErrorReason = async (
2754
- response: Response,
2755
- ): Promise<string | null> => {
2756
- let parsed: unknown;
2757
- try {
2758
- parsed = await response.json();
2759
- } catch {
2760
- return null;
2761
- }
2762
- if (!isRecord(parsed) || typeof parsed.message !== 'string') {
2763
- return null;
2764
- }
2765
- if (Array.isArray(parsed.errors) && parsed.errors.length > 0) {
2766
- const details = parsed.errors
2767
- .map((error) => {
2768
- if (typeof error === 'string') {
2769
- return error;
2770
- }
2771
- if (isRecord(error) && typeof error.message === 'string') {
2772
- return error.message;
2773
- }
2774
- return '';
2775
- })
2776
- .filter((detail) => detail.length > 0)
2777
- .join('; ');
2778
- if (details.length > 0) {
2779
- return `${parsed.message}: ${details}`;
2780
- }
2781
- }
2782
- return parsed.message;
2783
- };
2784
-
2785
- private formatGitHubErrorWithStatus = async (
2786
- response: Response,
2787
- ): Promise<string> => {
2788
- const status = `HTTP ${response.status}`;
2789
- const bodyText = await response.clone().text();
2790
- const reason = await this.readGitHubErrorReason(response);
2791
- if (hasRateLimitSignals(response.status, response.headers, bodyText)) {
2792
- const resetIso = computeRateLimitResetIso(response.headers);
2793
- const resetSuffix = resetIso === null ? '' : ` (resets at ${resetIso})`;
2794
- return `${status} GitHub rate limit exceeded, please retry shortly${resetSuffix}`;
2795
- }
2796
- if (response.status === 403) {
2797
- const permissionSuffix = reason === null ? '' : ` ${reason}`;
2798
- return `${status} permission denied, the token cannot perform this operation${permissionSuffix}`;
2799
- }
2800
- if (reason === null) {
2801
- return status;
2802
- }
2803
- return `${status} ${reason}`;
2804
- };
2805
-
2806
- updateBranch = async (prUrl: string): Promise<boolean> => {
2807
- const { owner, repo, issueNumber: prNumber } = this.parseIssueUrl(prUrl);
2808
- const response = await this.fetchWithRateLimitRetry(() =>
2809
- fetch(
2810
- `https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/pulls/${prNumber}/update-branch`,
2811
- {
2812
- method: 'PUT',
2813
- headers: {
2814
- Authorization: `Bearer ${this.ghToken}`,
2815
- Accept: 'application/vnd.github+json',
2816
- },
2817
- },
2818
- ),
2819
- );
2820
- if (response.ok) {
2821
- return true;
2822
- }
2823
- if (response.status === 422) {
2824
- return false;
2825
- }
2826
- const reason = await this.formatGitHubErrorWithStatus(response);
2827
- throw new Error(`Failed to update branch for PR ${prUrl}: ${reason}`);
2828
- };
2829
-
2830
- deletePullRequestBranch = async (
2831
- prUrl: string,
2832
- branchName: string,
2833
- ): Promise<void> => {
2834
- const { owner, repo } = this.parseIssueUrl(prUrl);
2835
- const response = await this.fetchWithRateLimitRetry(() =>
2836
- fetch(
2837
- `https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/git/refs/heads/${encodeURIComponent(branchName)}`,
2838
- {
2839
- method: 'DELETE',
2840
- headers: {
2841
- Authorization: `Bearer ${this.ghToken}`,
2842
- },
2843
- },
2844
- ),
2845
- );
2846
- if (!response.ok && response.status !== 422) {
2847
- const reason = await this.formatGitHubErrorWithStatus(response);
2848
- throw new Error(
2849
- `Failed to delete branch ${branchName} for PR ${prUrl}: ${reason}`,
2850
- );
2851
- }
2852
- };
2853
-
2854
- createCommentByUrl = async (
2855
- issueOrPrUrl: string,
2856
- commentBody: string,
2857
- ): Promise<void> => {
2858
- await this.restIssueRepository.createComment(issueOrPrUrl, commentBody);
2859
- };
2860
-
2861
- private fetchIssueBodyResponse = (url: string): Promise<Response> => {
2862
- const { owner, repo, issueNumber } = this.parseIssueUrl(url);
2863
- return this.fetchWithRateLimitRetry(() =>
2864
- fetch(
2865
- `https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/issues/${issueNumber}`,
2866
- {
2867
- method: 'GET',
2868
- headers: {
2869
- Authorization: `Bearer ${this.ghToken}`,
2870
- Accept: 'application/vnd.github+json',
2871
- },
2872
- },
2873
- ),
2874
- );
2875
- };
2876
-
2877
- private parseIssueBodyResponse = async (
2878
- url: string,
2879
- response: Response,
2880
- ): Promise<string> => {
2881
- const body: unknown = await response.json();
2882
- if (!isIssueOrPullRequestBodyResponse(body)) {
2883
- throw new Error(
2884
- `Unexpected response shape when fetching body for ${url}`,
2885
- );
2886
- }
2887
- return body.body ?? '';
2888
- };
2889
-
2890
- getIssueOrPullRequestBody = async (url: string): Promise<string> => {
2891
- const response = await this.fetchIssueBodyResponse(url);
2892
- if (!response.ok) {
2893
- await this.throwGitHubError(`Failed to fetch body for ${url}`, response);
2894
- }
2895
- return this.parseIssueBodyResponse(url, response);
2896
- };
2897
-
2898
- getIssueBodyByUrl = async (url: string): Promise<string | null> => {
2899
- const response = await this.fetchIssueBodyResponse(url);
2900
- if (response.status === 404) {
2901
- return null;
2902
- }
2903
- if (!response.ok) {
2904
- const reason = await this.formatGitHubErrorWithStatus(response);
2905
- throw new Error(`Failed to fetch body for ${url}: ${reason}`);
2906
- }
2907
- return this.parseIssueBodyResponse(url, response);
2908
- };
2909
-
2910
- getIssueOrPullRequestComments = async (
2911
- url: string,
2912
- ): Promise<IssueComment[]> => {
2913
- const { owner, repo, issueNumber } = this.parseIssueUrl(url);
2914
- const perPage = 100;
2915
- const collectedComments: IssueComment[] = [];
2916
- let page = 1;
2917
- let hasMore = true;
2918
- while (hasMore) {
2919
- const response = await this.fetchWithRateLimitRetry(() =>
2920
- fetch(
2921
- `https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/issues/${issueNumber}/comments?per_page=${perPage}&page=${page}`,
2922
- {
2923
- method: 'GET',
2924
- headers: {
2925
- Authorization: `Bearer ${this.ghToken}`,
2926
- Accept: 'application/vnd.github+json',
2927
- },
2928
- },
2929
- ),
2930
- );
2931
- if (!response.ok) {
2932
- await this.throwGitHubError(
2933
- `Failed to fetch comments for ${url}`,
2934
- response,
2935
- );
2936
- }
2937
- const body: unknown = await response.json();
2938
- if (!isIssueCommentsResponse(body)) {
2939
- throw new Error(
2940
- `Unexpected response shape when fetching comments for ${url}`,
2941
- );
2942
- }
2943
- for (const comment of body) {
2944
- collectedComments.push({
2945
- author: comment.user?.login ?? '',
2946
- body: comment.body ?? '',
2947
- createdAt: new Date(comment.created_at),
2948
- });
2949
- }
2950
- if (body.length < perPage) {
2951
- hasMore = false;
2952
- } else {
2953
- page += 1;
2954
- }
2955
- }
2956
- return collectedComments;
2957
- };
2958
-
2959
- getPullRequestDetail = async (
2960
- prUrl: string,
2961
- ): Promise<PullRequestDetail | null> => {
2962
- const {
2963
- owner,
2964
- repo,
2965
- issueNumber: prNumber,
2966
- isPr,
2967
- } = this.parseIssueUrl(prUrl);
2968
- if (!isPr) {
2969
- return null;
2970
- }
2971
- const detailResponse = await this.fetchWithRateLimitRetry(() =>
2972
- fetch(
2973
- `https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/pulls/${prNumber}`,
2974
- {
2975
- method: 'GET',
2976
- headers: {
2977
- Authorization: `Bearer ${this.ghToken}`,
2978
- Accept: 'application/vnd.github+json',
2979
- },
2980
- },
2981
- ),
2982
- );
2983
- if (!detailResponse.ok) {
2984
- await this.throwGitHubError(
2985
- `Failed to fetch detail for PR ${prUrl}`,
2986
- detailResponse,
2987
- );
2988
- }
2989
- const detailBody: unknown = await detailResponse.json();
2990
- if (!isPullRequestDetailResponse(detailBody)) {
2991
- throw new Error(
2992
- `Unexpected response shape when fetching detail for PR ${prUrl}`,
2993
- );
2994
- }
2995
- const files = await this.fetchPullRequestFiles(
2996
- owner,
2997
- repo,
2998
- prNumber,
2999
- prUrl,
3000
- );
3001
- return {
3002
- title: detailBody.title,
3003
- state: detailBody.state,
3004
- merged: detailBody.merged,
3005
- isDraft: detailBody.draft,
3006
- additions: detailBody.additions,
3007
- deletions: detailBody.deletions,
3008
- changedFiles: detailBody.changed_files,
3009
- headRefName: detailBody.head.ref,
3010
- baseRefName: detailBody.base.ref,
3011
- author: detailBody.user?.login ?? '',
3012
- files,
3013
- };
3014
- };
3015
-
3016
- private fetchPullRequestFiles = async (
3017
- owner: string,
3018
- repo: string,
3019
- prNumber: number,
3020
- prUrl: string,
3021
- ): Promise<PullRequestFile[]> => {
3022
- const perPage = 100;
3023
- const collectedFiles: PullRequestFile[] = [];
3024
- let page = 1;
3025
- let hasMore = true;
3026
- while (hasMore) {
3027
- const response = await this.fetchWithRateLimitRetry(() =>
3028
- fetch(
3029
- `https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/pulls/${prNumber}/files?per_page=${perPage}&page=${page}`,
3030
- {
3031
- method: 'GET',
3032
- headers: {
3033
- Authorization: `Bearer ${this.ghToken}`,
3034
- Accept: 'application/vnd.github+json',
3035
- },
3036
- },
3037
- ),
3038
- );
3039
- if (!response.ok) {
3040
- await this.throwGitHubError(
3041
- `Failed to fetch files for PR ${prUrl}`,
3042
- response,
3043
- );
3044
- }
3045
- const body: unknown = await response.json();
3046
- if (!isPullRequestDetailFilesResponse(body)) {
3047
- throw new Error(
3048
- `Unexpected response shape when fetching files for PR ${prUrl}`,
3049
- );
3050
- }
3051
- for (const file of body) {
3052
- collectedFiles.push({
3053
- filename: file.filename,
3054
- status: file.status,
3055
- additions: file.additions,
3056
- deletions: file.deletions,
3057
- patch: file.patch ?? null,
3058
- rawUrl:
3059
- file.raw_url === undefined
3060
- ? null
3061
- : normalizeGitHubRawUrl(file.raw_url),
3062
- });
3063
- }
3064
- if (body.length < perPage) {
3065
- hasMore = false;
3066
- } else {
3067
- page += 1;
3068
- }
3069
- }
3070
- return collectedFiles;
3071
- };
3072
-
3073
- getPullRequestCommits = async (
3074
- prUrl: string,
3075
- ): Promise<PullRequestCommit[]> => {
3076
- const {
3077
- owner,
3078
- repo,
3079
- issueNumber: prNumber,
3080
- isPr,
3081
- } = this.parseIssueUrl(prUrl);
3082
- if (!isPr) {
3083
- return [];
3084
- }
3085
- const perPage = 100;
3086
- const collectedCommits: PullRequestCommit[] = [];
3087
- let page = 1;
3088
- let hasMore = true;
3089
- while (hasMore) {
3090
- const response = await this.fetchWithRateLimitRetry(() =>
3091
- fetch(
3092
- `https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/pulls/${prNumber}/commits?per_page=${perPage}&page=${page}`,
3093
- {
3094
- method: 'GET',
3095
- headers: {
3096
- Authorization: `Bearer ${this.ghToken}`,
3097
- Accept: 'application/vnd.github+json',
3098
- },
3099
- },
3100
- ),
3101
- );
3102
- if (!response.ok) {
3103
- await this.throwGitHubError(
3104
- `Failed to fetch commits for PR ${prUrl}`,
3105
- response,
3106
- );
3107
- }
3108
- const body: unknown = await response.json();
3109
- if (!isPullRequestCommitsResponse(body)) {
3110
- throw new Error(
3111
- `Unexpected response shape when fetching commits for PR ${prUrl}`,
3112
- );
3113
- }
3114
- for (const commit of body) {
3115
- collectedCommits.push({
3116
- sha: commit.sha,
3117
- message: commit.commit.message,
3118
- author: commit.commit.author?.name ?? '',
3119
- authoredAt: new Date(commit.commit.author?.date ?? 0),
3120
- });
3121
- }
3122
- if (body.length < perPage) {
3123
- hasMore = false;
3124
- } else {
3125
- page += 1;
3126
- }
3127
- }
3128
- return collectedCommits;
3129
- };
3130
-
3131
- getIssueOrPullRequestState = async (
3132
- url: string,
3133
- ): Promise<{
3134
- state: string;
3135
- merged: boolean;
3136
- isPullRequest: boolean;
3137
- title: string;
3138
- }> => {
3139
- const { owner, repo, issueNumber, isPr } = this.parseIssueUrl(url);
3140
- if (isPr) {
3141
- const response = await this.fetchWithRateLimitRetry(() =>
3142
- fetch(
3143
- `https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/pulls/${issueNumber}`,
3144
- {
3145
- method: 'GET',
3146
- headers: {
3147
- Authorization: `Bearer ${this.ghToken}`,
3148
- Accept: 'application/vnd.github+json',
3149
- },
3150
- },
3151
- ),
3152
- );
3153
- if (!response.ok) {
3154
- await this.throwGitHubError(
3155
- `Failed to fetch state for ${url}`,
3156
- response,
3157
- );
3158
- }
3159
- const body: unknown = await response.json();
3160
- if (!isPullRequestDetailResponse(body)) {
3161
- throw new Error(
3162
- `Unexpected response shape when fetching state for ${url}`,
3163
- );
3164
- }
3165
- return {
3166
- state: body.state,
3167
- merged: body.merged,
3168
- isPullRequest: true,
3169
- title: body.title,
3170
- };
3171
- }
3172
- const response = await this.fetchWithRateLimitRetry(() =>
3173
- fetch(
3174
- `https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/issues/${issueNumber}`,
3175
- {
3176
- method: 'GET',
3177
- headers: {
3178
- Authorization: `Bearer ${this.ghToken}`,
3179
- Accept: 'application/vnd.github+json',
3180
- },
3181
- },
3182
- ),
3183
- );
3184
- if (!response.ok) {
3185
- await this.throwGitHubError(`Failed to fetch state for ${url}`, response);
3186
- }
3187
- const body: unknown = await response.json();
3188
- if (!isIssueOrPullRequestStateResponse(body)) {
3189
- throw new Error(
3190
- `Unexpected response shape when fetching state for ${url}`,
3191
- );
3192
- }
3193
- return {
3194
- state: body.state,
3195
- merged: false,
3196
- isPullRequest: false,
3197
- title: body.title,
3198
- };
3199
- };
3200
-
3201
- getPullRequestSummary = async (
3202
- prUrl: string,
3203
- ): Promise<{
3204
- title: string;
3205
- body: string;
3206
- additions: number;
3207
- deletions: number;
3208
- changedFiles: number;
3209
- } | null> => {
3210
- const {
3211
- owner,
3212
- repo,
3213
- issueNumber: prNumber,
3214
- isPr,
3215
- } = this.parseIssueUrl(prUrl);
3216
- if (!isPr) {
3217
- return null;
3218
- }
3219
- const response = await this.fetchWithRateLimitRetry(() =>
3220
- fetch(
3221
- `https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/pulls/${prNumber}`,
3222
- {
3223
- method: 'GET',
3224
- headers: {
3225
- Authorization: `Bearer ${this.ghToken}`,
3226
- Accept: 'application/vnd.github+json',
3227
- },
3228
- },
3229
- ),
3230
- );
3231
- if (!response.ok) {
3232
- const reason = await this.formatGitHubErrorWithStatus(response);
3233
- throw new Error(`Failed to fetch summary for PR ${prUrl}: ${reason}`);
3234
- }
3235
- const body: unknown = await response.json();
3236
- if (!isPullRequestDetailResponse(body)) {
3237
- throw new Error(
3238
- `Unexpected response shape when fetching summary for PR ${prUrl}`,
3239
- );
3240
- }
3241
- return {
3242
- title: body.title,
3243
- body: body.body ?? '',
3244
- additions: body.additions,
3245
- deletions: body.deletions,
3246
- changedFiles: body.changed_files,
3247
- };
3248
- };
3249
-
3250
- deleteAllCommentsByUrl = async (issueOrPrUrl: string): Promise<void> => {
3251
- const { owner, repo, issueNumber } = this.parseIssueUrl(issueOrPrUrl);
3252
- const perPage = 100;
3253
- while (true) {
3254
- const response = await this.fetchWithRateLimitRetry(() =>
3255
- fetch(
3256
- `https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/issues/${issueNumber}/comments?per_page=${perPage}&page=1`,
3257
- {
3258
- method: 'GET',
3259
- headers: {
3260
- Authorization: `Bearer ${this.ghToken}`,
3261
- Accept: 'application/vnd.github+json',
3262
- },
3263
- },
3264
- ),
3265
- );
3266
- if (!response.ok) {
3267
- await this.throwGitHubError(
3268
- `Failed to fetch comments for ${issueOrPrUrl}`,
3269
- response,
3270
- );
3271
- }
3272
- const body: unknown = await response.json();
3273
- if (!isIssueCommentIdResponse(body)) {
3274
- throw new Error(
3275
- `Unexpected response shape when fetching comments for ${issueOrPrUrl}`,
3276
- );
3277
- }
3278
- for (const comment of body) {
3279
- const deleteResponse = await this.fetchWithRateLimitRetry(() =>
3280
- fetch(
3281
- `https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/issues/comments/${comment.id}`,
3282
- {
3283
- method: 'DELETE',
3284
- headers: {
3285
- Authorization: `Bearer ${this.ghToken}`,
3286
- Accept: 'application/vnd.github+json',
3287
- },
3288
- },
3289
- ),
3290
- );
3291
- if (!deleteResponse.ok) {
3292
- await this.throwGitHubError(
3293
- `Failed to delete comment ${comment.id} on ${issueOrPrUrl}`,
3294
- deleteResponse,
3295
- );
3296
- }
3297
- }
3298
- if (body.length < perPage) {
3299
- break;
3300
- }
3301
- }
3302
- };
3303
- }