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

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 (780) hide show
  1. package/bin/adapter/entry-points/cli/index.js +0 -0
  2. package/package.json +5 -1
  3. package/.env.example +0 -7
  4. package/.gitattributes +0 -2
  5. package/.github/CODEOWNERS +0 -6
  6. package/.github/copilot-instructions.md +0 -6
  7. package/.github/dependabot.yml +0 -19
  8. package/.github/instructions/code-review.instructions.md +0 -50
  9. package/.github/workflows/api-created_issue_pr.yml +0 -97
  10. package/.github/workflows/ci-failure-issue-creator.yml +0 -48
  11. package/.github/workflows/close-manual-prs.yml +0 -80
  12. package/.github/workflows/codeql.yml +0 -107
  13. package/.github/workflows/commit-lint.yml +0 -85
  14. package/.github/workflows/configs/commitlint.config.cjs +0 -32
  15. package/.github/workflows/configs/commitlint.config.js +0 -32
  16. package/.github/workflows/console-ui.yml +0 -68
  17. package/.github/workflows/create-pr.yml +0 -116
  18. package/.github/workflows/empty-format-test-job.yml +0 -34
  19. package/.github/workflows/format.yml +0 -28
  20. package/.github/workflows/publish.yml +0 -111
  21. package/.github/workflows/secret-scan.yml +0 -51
  22. package/.github/workflows/test.yml +0 -58
  23. package/.github/workflows/umino-project.yml +0 -207
  24. package/.gitleaks.toml +0 -22
  25. package/.prettierignore +0 -31
  26. package/.prettierrc +0 -5
  27. package/CHANGELOG.md +0 -4162
  28. package/CONTRIBUTING.md +0 -60
  29. package/bin/adapter/entry-points/handlers/resolveUnansweredOwnerCalls.js +0 -12
  30. package/bin/adapter/entry-points/handlers/resolveUnansweredOwnerCalls.js.map +0 -1
  31. package/bin/adapter/repositories/FileSystemSilentSessionNotifiedStateRepository.js +0 -125
  32. package/bin/adapter/repositories/FileSystemSilentSessionNotifiedStateRepository.js.map +0 -1
  33. package/bin/adapter/repositories/issue/issueTimelineUtils.js +0 -38
  34. package/bin/adapter/repositories/issue/issueTimelineUtils.js.map +0 -1
  35. package/bin/domain/entities/UnansweredOwnerCall.js +0 -3
  36. package/bin/domain/entities/UnansweredOwnerCall.js.map +0 -1
  37. package/bin/domain/entities/WorkingTime.js +0 -3
  38. package/bin/domain/entities/WorkingTime.js.map +0 -1
  39. package/bin/domain/usecases/ChangeStatusLongInReviewIssueUseCase.js +0 -37
  40. package/bin/domain/usecases/ChangeStatusLongInReviewIssueUseCase.js.map +0 -1
  41. package/bin/domain/usecases/ConvertCheckboxToIssueInStoryIssueUseCase.js +0 -118
  42. package/bin/domain/usecases/ConvertCheckboxToIssueInStoryIssueUseCase.js.map +0 -1
  43. package/bin/domain/usecases/GenerateWorkingTimeReportUseCase.js +0 -180
  44. package/bin/domain/usecases/GenerateWorkingTimeReportUseCase.js.map +0 -1
  45. package/bin/domain/usecases/TriagerApprovalDispatchUseCase.js +0 -160
  46. package/bin/domain/usecases/TriagerApprovalDispatchUseCase.js.map +0 -1
  47. package/bin/domain/usecases/adapter-interfaces/SilentSessionNotifiedStateRepository.js +0 -3
  48. package/bin/domain/usecases/adapter-interfaces/SilentSessionNotifiedStateRepository.js.map +0 -1
  49. package/bin/domain/usecases/awaitingOwnerApprovalMessage.js +0 -7
  50. package/bin/domain/usecases/awaitingOwnerApprovalMessage.js.map +0 -1
  51. package/bin/domain/usecases/findLastAgentDeclaringReport.js +0 -12
  52. package/bin/domain/usecases/findLastAgentDeclaringReport.js.map +0 -1
  53. package/bin/domain/usecases/isPullRequestDeclaredUnnecessary.js +0 -25
  54. package/bin/domain/usecases/isPullRequestDeclaredUnnecessary.js.map +0 -1
  55. package/bin/domain/usecases/isWaitingForOwnerApproval.js +0 -16
  56. package/bin/domain/usecases/isWaitingForOwnerApproval.js.map +0 -1
  57. package/bin/domain/usecases/returnedToAwaitingWorkspaceMessage.js +0 -7
  58. package/bin/domain/usecases/returnedToAwaitingWorkspaceMessage.js.map +0 -1
  59. package/commitlint.config.js +0 -6
  60. package/eslint.config.mjs +0 -76
  61. package/jest.config.js +0 -84
  62. package/jest.setup.js +0 -1
  63. package/lib/buffer-equal-constant-time-compat/index.js +0 -17
  64. package/lib/buffer-equal-constant-time-compat/index.test.js +0 -54
  65. package/lib/buffer-equal-constant-time-compat/package.json +0 -5
  66. package/lib/typescript-compat/bin/tsc +0 -2
  67. package/lib/typescript-compat/bin/tsserver +0 -2
  68. package/lib/typescript-compat/lib/typescript.js +0 -1
  69. package/lib/typescript-compat/package.json +0 -13
  70. package/renovate.json +0 -46
  71. package/scripts/copyConsoleUiDist.mjs +0 -35
  72. package/scripts/defaultBranchTipVerify.sh +0 -23
  73. package/scripts/testWorkflowRunVerify.sh +0 -47
  74. package/scripts/verifyConsoleUiDist.mjs +0 -96
  75. package/src/adapter/ci/consoleUiWorkflowTimeout.test.ts +0 -61
  76. package/src/adapter/ci/publishTestWorkflowGate.test.ts +0 -653
  77. package/src/adapter/ci/workflowRunCancellation.test.ts +0 -72
  78. package/src/adapter/entry-points/cli/fleetConfig.test.ts +0 -553
  79. package/src/adapter/entry-points/cli/fleetConfig.ts +0 -266
  80. package/src/adapter/entry-points/cli/index.test.ts +0 -2837
  81. package/src/adapter/entry-points/cli/index.ts +0 -1488
  82. package/src/adapter/entry-points/cli/projectConfig.test.ts +0 -342
  83. package/src/adapter/entry-points/cli/projectConfig.ts +0 -500
  84. package/src/adapter/entry-points/console/consoleAirplaneSnapshotApi.test.ts +0 -658
  85. package/src/adapter/entry-points/console/consoleAirplaneSnapshotApi.ts +0 -482
  86. package/src/adapter/entry-points/console/consoleDataDelivery.test.ts +0 -222
  87. package/src/adapter/entry-points/console/consoleDataDelivery.ts +0 -134
  88. package/src/adapter/entry-points/console/consoleDoneStore.test.ts +0 -169
  89. package/src/adapter/entry-points/console/consoleDoneStore.ts +0 -139
  90. package/src/adapter/entry-points/console/consoleGithubTokenResolver.test.ts +0 -275
  91. package/src/adapter/entry-points/console/consoleGithubTokenResolver.ts +0 -113
  92. package/src/adapter/entry-points/console/consoleImageProxy.test.ts +0 -140
  93. package/src/adapter/entry-points/console/consoleImageProxy.ts +0 -111
  94. package/src/adapter/entry-points/console/consoleItemUrlLookup.test.ts +0 -63
  95. package/src/adapter/entry-points/console/consoleItemUrlLookup.ts +0 -66
  96. package/src/adapter/entry-points/console/consoleOperationApi.test.ts +0 -2523
  97. package/src/adapter/entry-points/console/consoleOperationApi.ts +0 -936
  98. package/src/adapter/entry-points/console/consoleProjectResolver.test.ts +0 -246
  99. package/src/adapter/entry-points/console/consoleProjectResolver.ts +0 -111
  100. package/src/adapter/entry-points/console/consoleReadApi.test.ts +0 -714
  101. package/src/adapter/entry-points/console/consoleReadApi.ts +0 -361
  102. package/src/adapter/entry-points/console/consoleTabNames.test.ts +0 -52
  103. package/src/adapter/entry-points/console/consoleTabNames.ts +0 -37
  104. package/src/adapter/entry-points/console/consoleUiDistSync.test.ts +0 -66
  105. package/src/adapter/entry-points/console/dashboardComposeService.test.ts +0 -595
  106. package/src/adapter/entry-points/console/dashboardComposeService.ts +0 -256
  107. package/src/adapter/entry-points/console/dashboardEmitterComposerKey.test.ts +0 -180
  108. package/src/adapter/entry-points/console/ensureConsoleRunning.test.ts +0 -115
  109. package/src/adapter/entry-points/console/ensureConsoleRunning.ts +0 -64
  110. package/src/adapter/entry-points/console/ui/.storybook/main.ts +0 -12
  111. package/src/adapter/entry-points/console/ui/.storybook/preview.ts +0 -15
  112. package/src/adapter/entry-points/console/ui/biome.json +0 -53
  113. package/src/adapter/entry-points/console/ui/components.json +0 -20
  114. package/src/adapter/entry-points/console/ui/e2e/consoleScenario.spec.ts +0 -725
  115. package/src/adapter/entry-points/console/ui/e2e/consoleTestHarness.ts +0 -831
  116. package/src/adapter/entry-points/console/ui/e2e/playwright.config.ts +0 -30
  117. package/src/adapter/entry-points/console/ui/e2e/tsconfig.json +0 -18
  118. package/src/adapter/entry-points/console/ui/index.html +0 -12
  119. package/src/adapter/entry-points/console/ui/jest-globals.cjs +0 -26
  120. package/src/adapter/entry-points/console/ui/jest.setup.ts +0 -1
  121. package/src/adapter/entry-points/console/ui/jest.styleMock.js +0 -1
  122. package/src/adapter/entry-points/console/ui/public/sw.js +0 -60
  123. package/src/adapter/entry-points/console/ui/src/components/ui/badge.stories.tsx +0 -35
  124. package/src/adapter/entry-points/console/ui/src/components/ui/badge.tsx +0 -28
  125. package/src/adapter/entry-points/console/ui/src/components/ui/button.stories.tsx +0 -34
  126. package/src/adapter/entry-points/console/ui/src/components/ui/button.tsx +0 -50
  127. package/src/adapter/entry-points/console/ui/src/features/console/components/content/ConsoleCopyCodeButton.stories.tsx +0 -28
  128. package/src/adapter/entry-points/console/ui/src/features/console/components/content/ConsoleCopyCodeButton.test.tsx +0 -65
  129. package/src/adapter/entry-points/console/ui/src/features/console/components/content/ConsoleCopyCodeButton.tsx +0 -19
  130. package/src/adapter/entry-points/console/ui/src/features/console/components/content/ConsoleMarkdownContent.stories.tsx +0 -61
  131. package/src/adapter/entry-points/console/ui/src/features/console/components/content/ConsoleMarkdownContent.test.tsx +0 -199
  132. package/src/adapter/entry-points/console/ui/src/features/console/components/content/ConsoleMarkdownContent.tsx +0 -166
  133. package/src/adapter/entry-points/console/ui/src/features/console/components/content/ConsoleMermaidDiagram.stories.tsx +0 -22
  134. package/src/adapter/entry-points/console/ui/src/features/console/components/content/ConsoleMermaidDiagram.test.tsx +0 -74
  135. package/src/adapter/entry-points/console/ui/src/features/console/components/content/ConsoleMermaidDiagram.tsx +0 -84
  136. package/src/adapter/entry-points/console/ui/src/features/console/components/content/ConsoleMermaidDiagramModalScreen.stories.tsx +0 -27
  137. package/src/adapter/entry-points/console/ui/src/features/console/components/content/ConsoleMermaidDiagramModalScreen.test.tsx +0 -85
  138. package/src/adapter/entry-points/console/ui/src/features/console/components/content/ConsoleMermaidDiagramModalScreen.tsx +0 -97
  139. package/src/adapter/entry-points/console/ui/src/features/console/components/content/ConsoleReferenceLink.stories.tsx +0 -76
  140. package/src/adapter/entry-points/console/ui/src/features/console/components/content/ConsoleReferenceLink.test.tsx +0 -56
  141. package/src/adapter/entry-points/console/ui/src/features/console/components/content/ConsoleReferenceLink.tsx +0 -39
  142. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleChangedFileList.stories.tsx +0 -32
  143. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleChangedFileList.test.tsx +0 -105
  144. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleChangedFileList.tsx +0 -157
  145. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleCommentComposer.stories.tsx +0 -68
  146. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleCommentComposer.test.tsx +0 -612
  147. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleCommentComposer.tsx +0 -276
  148. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleCommentList.stories.tsx +0 -33
  149. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleCommentList.test.tsx +0 -57
  150. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleCommentList.tsx +0 -76
  151. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleCommitList.stories.tsx +0 -25
  152. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleCommitList.test.tsx +0 -55
  153. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleCommitList.tsx +0 -49
  154. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleCopyUrlButton.stories.tsx +0 -24
  155. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleCopyUrlButton.test.tsx +0 -63
  156. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleCopyUrlButton.tsx +0 -19
  157. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleFetchFailureAlert.stories.tsx +0 -33
  158. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleFetchFailureAlert.test.tsx +0 -62
  159. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleFetchFailureAlert.tsx +0 -59
  160. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleFileDiff.stories.tsx +0 -38
  161. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleFileDiff.test.tsx +0 -151
  162. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleFileDiff.tsx +0 -245
  163. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleItemDetail.stories.tsx +0 -514
  164. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleItemDetail.test.tsx +0 -727
  165. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleItemDetail.tsx +0 -387
  166. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleItemIcon.stories.tsx +0 -82
  167. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleItemIcon.test.tsx +0 -52
  168. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleItemIcon.tsx +0 -32
  169. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsolePullRequestDetail.stories.tsx +0 -48
  170. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsolePullRequestDetail.test.tsx +0 -235
  171. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsolePullRequestDetail.tsx +0 -145
  172. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsolePullRequestMergeableChip.stories.tsx +0 -29
  173. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsolePullRequestMergeableChip.test.tsx +0 -30
  174. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsolePullRequestMergeableChip.tsx +0 -50
  175. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsolePullRequestStatusBadges.stories.tsx +0 -38
  176. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsolePullRequestStatusBadges.test.tsx +0 -54
  177. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsolePullRequestStatusBadges.tsx +0 -60
  178. package/src/adapter/entry-points/console/ui/src/features/console/components/layout/ConsoleAirplaneModeButton.stories.tsx +0 -56
  179. package/src/adapter/entry-points/console/ui/src/features/console/components/layout/ConsoleAirplaneModeButton.test.tsx +0 -111
  180. package/src/adapter/entry-points/console/ui/src/features/console/components/layout/ConsoleAirplaneModeButton.tsx +0 -92
  181. package/src/adapter/entry-points/console/ui/src/features/console/components/layout/ConsolePanel.stories.tsx +0 -51
  182. package/src/adapter/entry-points/console/ui/src/features/console/components/layout/ConsolePanel.test.tsx +0 -59
  183. package/src/adapter/entry-points/console/ui/src/features/console/components/layout/ConsolePanel.tsx +0 -39
  184. package/src/adapter/entry-points/console/ui/src/features/console/components/layout/ConsoleTabList.stories.tsx +0 -207
  185. package/src/adapter/entry-points/console/ui/src/features/console/components/layout/ConsoleTabList.test.tsx +0 -332
  186. package/src/adapter/entry-points/console/ui/src/features/console/components/layout/ConsoleTabList.tsx +0 -211
  187. package/src/adapter/entry-points/console/ui/src/features/console/components/layout/ConsoleTimerSettingsModalDialog.stories.tsx +0 -49
  188. package/src/adapter/entry-points/console/ui/src/features/console/components/layout/ConsoleTimerSettingsModalDialog.test.tsx +0 -157
  189. package/src/adapter/entry-points/console/ui/src/features/console/components/layout/ConsoleTimerSettingsModalDialog.tsx +0 -118
  190. package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleCopyStoryNameButton.stories.tsx +0 -23
  191. package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleCopyStoryNameButton.test.tsx +0 -40
  192. package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleCopyStoryNameButton.tsx +0 -17
  193. package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleItemList.stories.tsx +0 -101
  194. package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleItemList.test.tsx +0 -102
  195. package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleItemList.tsx +0 -75
  196. package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleItemSummary.stories.tsx +0 -40
  197. package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleItemSummary.test.tsx +0 -213
  198. package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleItemSummary.tsx +0 -115
  199. package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleQueuedItemRow.stories.tsx +0 -50
  200. package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleQueuedItemRow.test.tsx +0 -98
  201. package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleQueuedItemRow.tsx +0 -66
  202. package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleQueuedList.stories.tsx +0 -69
  203. package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleQueuedList.test.tsx +0 -101
  204. package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleQueuedList.tsx +0 -75
  205. package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleStoryList.stories.tsx +0 -137
  206. package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleStoryList.test.tsx +0 -554
  207. package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleStoryList.tsx +0 -400
  208. package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleStoryReorderPanel.stories.tsx +0 -30
  209. package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleStoryReorderPanel.test.tsx +0 -111
  210. package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleStoryReorderPanel.tsx +0 -91
  211. package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleStorySummary.stories.tsx +0 -27
  212. package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleStorySummary.test.tsx +0 -24
  213. package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleStorySummary.tsx +0 -28
  214. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleCloseActions.stories.tsx +0 -14
  215. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleCloseActions.test.tsx +0 -21
  216. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleCloseActions.tsx +0 -26
  217. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleDangerousActions.stories.tsx +0 -14
  218. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleDangerousActions.test.tsx +0 -40
  219. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleDangerousActions.tsx +0 -37
  220. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleErrorToast.stories.tsx +0 -33
  221. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleNextActionDateActions.stories.tsx +0 -20
  222. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleNextActionDateActions.test.tsx +0 -48
  223. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleNextActionDateActions.tsx +0 -42
  224. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleOfflinePendingActionsPanel.stories.tsx +0 -115
  225. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleOfflinePendingActionsPanel.test.tsx +0 -262
  226. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleOfflinePendingActionsPanel.tsx +0 -123
  227. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleOperationMenu.stories.tsx +0 -65
  228. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleOperationMenu.test.tsx +0 -159
  229. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleOperationMenu.tsx +0 -55
  230. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsolePullRequestReviewActions.stories.tsx +0 -20
  231. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsolePullRequestReviewActions.test.tsx +0 -93
  232. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsolePullRequestReviewActions.tsx +0 -45
  233. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleStatusActions.stories.tsx +0 -17
  234. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleStatusActions.test.tsx +0 -49
  235. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleStatusActions.tsx +0 -66
  236. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleUndoToast.stories.tsx +0 -47
  237. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleUndoToast.test.tsx +0 -95
  238. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleUndoToast.tsx +0 -62
  239. package/src/adapter/entry-points/console/ui/src/features/console/components/shared/ConsoleClipboardCopyButton.stories.tsx +0 -32
  240. package/src/adapter/entry-points/console/ui/src/features/console/components/shared/ConsoleClipboardCopyButton.test.tsx +0 -101
  241. package/src/adapter/entry-points/console/ui/src/features/console/components/shared/ConsoleClipboardCopyButton.tsx +0 -80
  242. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useAirplaneMode.test.ts +0 -304
  243. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useAirplaneMode.ts +0 -167
  244. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleActionQueue.test.ts +0 -498
  245. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleActionQueue.ts +0 -277
  246. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleCaches.test.ts +0 -99
  247. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleCaches.ts +0 -90
  248. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleDetailPrefetch.test.ts +0 -110
  249. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleDetailPrefetch.ts +0 -83
  250. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleFeaturesConfig.test.ts +0 -51
  251. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleFeaturesConfig.ts +0 -39
  252. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleItemDetailData.test.ts +0 -229
  253. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleItemDetailData.ts +0 -194
  254. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleNavigation.test.ts +0 -151
  255. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleNavigation.ts +0 -128
  256. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleOperations.test.ts +0 -629
  257. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleOperations.ts +0 -417
  258. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleOverlay.test.ts +0 -40
  259. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleOverlay.ts +0 -71
  260. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsolePjcode.test.ts +0 -24
  261. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsolePjcode.ts +0 -17
  262. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleProjectList.test.ts +0 -97
  263. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleProjectList.ts +0 -44
  264. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleProjectTimer.test.ts +0 -59
  265. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleProjectTimer.ts +0 -26
  266. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleReferenceLink.test.ts +0 -48
  267. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleReferenceLink.ts +0 -39
  268. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleResource.test.ts +0 -41
  269. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleResource.ts +0 -57
  270. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleSwipeNavigation.test.ts +0 -115
  271. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleSwipeNavigation.ts +0 -115
  272. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleTabData.test.ts +0 -546
  273. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleTabData.ts +0 -278
  274. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleTimerSettings.test.ts +0 -110
  275. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleTimerSettings.ts +0 -70
  276. package/src/adapter/entry-points/console/ui/src/features/console/lib/airplaneSnapshot.test.ts +0 -387
  277. package/src/adapter/entry-points/console/ui/src/features/console/lib/airplaneSnapshot.ts +0 -407
  278. package/src/adapter/entry-points/console/ui/src/features/console/lib/consoleApi.test.ts +0 -446
  279. package/src/adapter/entry-points/console/ui/src/features/console/lib/consoleApi.ts +0 -533
  280. package/src/adapter/entry-points/console/ui/src/features/console/lib/imageProxy.test.ts +0 -78
  281. package/src/adapter/entry-points/console/ui/src/features/console/lib/imageProxy.ts +0 -34
  282. package/src/adapter/entry-points/console/ui/src/features/console/lib/markdown.test.ts +0 -252
  283. package/src/adapter/entry-points/console/ui/src/features/console/lib/markdown.ts +0 -207
  284. package/src/adapter/entry-points/console/ui/src/features/console/lib/mermaidLoader.test.ts +0 -27
  285. package/src/adapter/entry-points/console/ui/src/features/console/lib/mermaidLoader.ts +0 -71
  286. package/src/adapter/entry-points/console/ui/src/features/console/lib/mermaidZoom.test.ts +0 -88
  287. package/src/adapter/entry-points/console/ui/src/features/console/lib/mermaidZoom.ts +0 -34
  288. package/src/adapter/entry-points/console/ui/src/features/console/lib/navigation.ts +0 -7
  289. package/src/adapter/entry-points/console/ui/src/features/console/lib/resourceCache.test.ts +0 -104
  290. package/src/adapter/entry-points/console/ui/src/features/console/lib/resourceCache.ts +0 -79
  291. package/src/adapter/entry-points/console/ui/src/features/console/logic/actionToast.test.ts +0 -200
  292. package/src/adapter/entry-points/console/ui/src/features/console/logic/actionToast.ts +0 -122
  293. package/src/adapter/entry-points/console/ui/src/features/console/logic/clipboard.test.ts +0 -90
  294. package/src/adapter/entry-points/console/ui/src/features/console/logic/clipboard.ts +0 -37
  295. package/src/adapter/entry-points/console/ui/src/features/console/logic/colors.test.ts +0 -34
  296. package/src/adapter/entry-points/console/ui/src/features/console/logic/colors.ts +0 -85
  297. package/src/adapter/entry-points/console/ui/src/features/console/logic/diff.test.ts +0 -81
  298. package/src/adapter/entry-points/console/ui/src/features/console/logic/diff.ts +0 -61
  299. package/src/adapter/entry-points/console/ui/src/features/console/logic/fileStatus.test.ts +0 -35
  300. package/src/adapter/entry-points/console/ui/src/features/console/logic/fileStatus.ts +0 -21
  301. package/src/adapter/entry-points/console/ui/src/features/console/logic/fileTree.test.ts +0 -96
  302. package/src/adapter/entry-points/console/ui/src/features/console/logic/fileTree.ts +0 -93
  303. package/src/adapter/entry-points/console/ui/src/features/console/logic/grouping.test.ts +0 -229
  304. package/src/adapter/entry-points/console/ui/src/features/console/logic/grouping.ts +0 -106
  305. package/src/adapter/entry-points/console/ui/src/features/console/logic/imageFile.test.ts +0 -18
  306. package/src/adapter/entry-points/console/ui/src/features/console/logic/imageFile.ts +0 -16
  307. package/src/adapter/entry-points/console/ui/src/features/console/logic/itemIcons.test.ts +0 -97
  308. package/src/adapter/entry-points/console/ui/src/features/console/logic/itemIcons.ts +0 -95
  309. package/src/adapter/entry-points/console/ui/src/features/console/logic/navigation.test.ts +0 -53
  310. package/src/adapter/entry-points/console/ui/src/features/console/logic/navigation.ts +0 -33
  311. package/src/adapter/entry-points/console/ui/src/features/console/logic/operations.test.ts +0 -72
  312. package/src/adapter/entry-points/console/ui/src/features/console/logic/operations.ts +0 -61
  313. package/src/adapter/entry-points/console/ui/src/features/console/logic/overlay.test.ts +0 -325
  314. package/src/adapter/entry-points/console/ui/src/features/console/logic/overlay.ts +0 -94
  315. package/src/adapter/entry-points/console/ui/src/features/console/logic/postedComments.test.ts +0 -44
  316. package/src/adapter/entry-points/console/ui/src/features/console/logic/postedComments.ts +0 -17
  317. package/src/adapter/entry-points/console/ui/src/features/console/logic/references.test.ts +0 -103
  318. package/src/adapter/entry-points/console/ui/src/features/console/logic/references.ts +0 -64
  319. package/src/adapter/entry-points/console/ui/src/features/console/logic/relativeTime.test.ts +0 -74
  320. package/src/adapter/entry-points/console/ui/src/features/console/logic/relativeTime.ts +0 -53
  321. package/src/adapter/entry-points/console/ui/src/features/console/logic/swipe.test.ts +0 -34
  322. package/src/adapter/entry-points/console/ui/src/features/console/logic/swipe.ts +0 -28
  323. package/src/adapter/entry-points/console/ui/src/features/console/logic/tabAdvance.test.ts +0 -89
  324. package/src/adapter/entry-points/console/ui/src/features/console/logic/tabAdvance.ts +0 -30
  325. package/src/adapter/entry-points/console/ui/src/features/console/logic/timerSettings.test.ts +0 -61
  326. package/src/adapter/entry-points/console/ui/src/features/console/logic/timerSettings.ts +0 -77
  327. package/src/adapter/entry-points/console/ui/src/features/console/logic/types.ts +0 -173
  328. package/src/adapter/entry-points/console/ui/src/features/console/pages/ConsoleItemDetailContainer.test.tsx +0 -629
  329. package/src/adapter/entry-points/console/ui/src/features/console/pages/ConsoleItemDetailContainer.tsx +0 -351
  330. package/src/adapter/entry-points/console/ui/src/features/console/pages/ConsolePage.test.tsx +0 -1469
  331. package/src/adapter/entry-points/console/ui/src/features/console/pages/ConsolePage.tsx +0 -736
  332. package/src/adapter/entry-points/console/ui/src/features/console/pages/ConsoleReferenceLinkContainer.tsx +0 -25
  333. package/src/adapter/entry-points/console/ui/src/features/console/testing/fixtures.ts +0 -418
  334. package/src/adapter/entry-points/console/ui/src/index.css +0 -1592
  335. package/src/adapter/entry-points/console/ui/src/lib/utils.ts +0 -4
  336. package/src/adapter/entry-points/console/ui/src/main.tsx +0 -21
  337. package/src/adapter/entry-points/console/ui/src/vite-env.d.ts +0 -1
  338. package/src/adapter/entry-points/console/ui/tsconfig.json +0 -26
  339. package/src/adapter/entry-points/console/ui/vite.config.ts +0 -24
  340. package/src/adapter/entry-points/console/ui-dist/assets/index-BYDbK1Fj.js +0 -88
  341. package/src/adapter/entry-points/console/ui-dist/assets/index-KyQISRuQ.css +0 -2
  342. package/src/adapter/entry-points/console/ui-dist/index.html +0 -13
  343. package/src/adapter/entry-points/console/ui-dist/sw.js +0 -60
  344. package/src/adapter/entry-points/console/webServer.test.ts +0 -2945
  345. package/src/adapter/entry-points/console/webServer.ts +0 -940
  346. package/src/adapter/entry-points/function/getStoryObjectMap.ts +0 -3
  347. package/src/adapter/entry-points/handlers/FileSystemConsoleTabsRepository.test.ts +0 -218
  348. package/src/adapter/entry-points/handlers/FileSystemConsoleTabsRepository.ts +0 -113
  349. package/src/adapter/entry-points/handlers/GetStoryObjectMapUseCaseHandler.test.ts +0 -110
  350. package/src/adapter/entry-points/handlers/GetStoryObjectMapUseCaseHandler.ts +0 -116
  351. package/src/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.test.ts +0 -859
  352. package/src/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.ts +0 -984
  353. package/src/adapter/entry-points/handlers/InTmuxByHumanSessionTokenCountHandler.test.ts +0 -126
  354. package/src/adapter/entry-points/handlers/InTmuxByHumanSessionTokenCountHandler.ts +0 -80
  355. package/src/adapter/entry-points/handlers/LiveSessionOauthTokenSelectHandler.test.ts +0 -491
  356. package/src/adapter/entry-points/handlers/LiveSessionOauthTokenSelectHandler.ts +0 -137
  357. package/src/adapter/entry-points/handlers/OauthTokenSelectHandler.test.ts +0 -508
  358. package/src/adapter/entry-points/handlers/OauthTokenSelectHandler.ts +0 -156
  359. package/src/adapter/entry-points/handlers/consoleListsWriter.test.ts +0 -332
  360. package/src/adapter/entry-points/handlers/consoleListsWriter.ts +0 -60
  361. package/src/adapter/entry-points/handlers/dashboardRowWriter.test.ts +0 -112
  362. package/src/adapter/entry-points/handlers/dashboardRowWriter.ts +0 -51
  363. package/src/adapter/entry-points/handlers/inTmuxByHumanDataWriter.test.ts +0 -285
  364. package/src/adapter/entry-points/handlers/inTmuxByHumanDataWriter.ts +0 -109
  365. package/src/adapter/entry-points/handlers/inTmuxByHumanSessionReconciler.test.ts +0 -170
  366. package/src/adapter/entry-points/handlers/inTmuxByHumanSessionReconciler.ts +0 -40
  367. package/src/adapter/entry-points/handlers/machineStatusWriter.test.ts +0 -191
  368. package/src/adapter/entry-points/handlers/machineStatusWriter.ts +0 -110
  369. package/src/adapter/entry-points/handlers/notifySilentTmuxSessions.test.ts +0 -548
  370. package/src/adapter/entry-points/handlers/notifySilentTmuxSessions.ts +0 -192
  371. package/src/adapter/entry-points/handlers/ownerCallFileCleaner.test.ts +0 -111
  372. package/src/adapter/entry-points/handlers/ownerCallFileCleaner.ts +0 -25
  373. package/src/adapter/entry-points/handlers/ownerCallFileStore.test.ts +0 -286
  374. package/src/adapter/entry-points/handlers/ownerCallFileStore.ts +0 -145
  375. package/src/adapter/entry-points/handlers/ownerReplyMarkerDirectoryResolve.test.ts +0 -55
  376. package/src/adapter/entry-points/handlers/ownerReplyMarkerDirectoryResolve.ts +0 -36
  377. package/src/adapter/entry-points/handlers/resetDegeneratedTmuxSessions.test.ts +0 -72
  378. package/src/adapter/entry-points/handlers/resetDegeneratedTmuxSessions.ts +0 -74
  379. package/src/adapter/entry-points/handlers/rotationOrderFileWriter.test.ts +0 -181
  380. package/src/adapter/entry-points/handlers/rotationOrderFileWriter.ts +0 -18
  381. package/src/adapter/entry-points/handlers/situationFileWriter.test.ts +0 -489
  382. package/src/adapter/entry-points/handlers/situationFileWriter.ts +0 -176
  383. package/src/adapter/entry-points/handlers/staleTmuxSessionCleaner.test.ts +0 -169
  384. package/src/adapter/entry-points/handlers/staleTmuxSessionCleaner.ts +0 -32
  385. package/src/adapter/entry-points/handlers/subscriptionDisabledTokenHandler.ts +0 -41
  386. package/src/adapter/entry-points/handlers/tokenExhaustionHandover.ts +0 -81
  387. package/src/adapter/entry-points/handlers/tokenStatusWriter.test.ts +0 -266
  388. package/src/adapter/entry-points/handlers/tokenStatusWriter.ts +0 -262
  389. package/src/adapter/proxy/ClaudeMessageResponseParser.test.ts +0 -211
  390. package/src/adapter/proxy/ClaudeMessageResponseParser.ts +0 -181
  391. package/src/adapter/proxy/RateLimitCache.test.ts +0 -1077
  392. package/src/adapter/proxy/RateLimitCache.ts +0 -385
  393. package/src/adapter/proxy/TokenListLoader.test.ts +0 -183
  394. package/src/adapter/proxy/TokenListLoader.ts +0 -75
  395. package/src/adapter/proxy/ensureProxyRunning.test.ts +0 -85
  396. package/src/adapter/proxy/ensureProxyRunning.ts +0 -41
  397. package/src/adapter/proxy/proxyEntry.test.ts +0 -552
  398. package/src/adapter/proxy/proxyEntry.ts +0 -214
  399. package/src/adapter/repositories/BaseGitHubRepository.test.ts +0 -26
  400. package/src/adapter/repositories/BaseGitHubRepository.ts +0 -26
  401. package/src/adapter/repositories/BrowserGitHubProjectRepository.test.ts +0 -108
  402. package/src/adapter/repositories/BrowserGitHubProjectRepository.ts +0 -118
  403. package/src/adapter/repositories/CliGitHubGraphqlRateLimitRepository.test.ts +0 -71
  404. package/src/adapter/repositories/CliGitHubGraphqlRateLimitRepository.ts +0 -21
  405. package/src/adapter/repositories/ConfigurableSilentSessionMessageComposer.test.ts +0 -324
  406. package/src/adapter/repositories/ConfigurableSilentSessionMessageComposer.ts +0 -148
  407. package/src/adapter/repositories/FetchWebhookRepository.ts +0 -8
  408. package/src/adapter/repositories/FileHandoverStateRepository.test.ts +0 -114
  409. package/src/adapter/repositories/FileHandoverStateRepository.ts +0 -73
  410. package/src/adapter/repositories/FileSystemInteractiveLiveSessionTranscriptResolver.test.ts +0 -514
  411. package/src/adapter/repositories/FileSystemInteractiveLiveSessionTranscriptResolver.ts +0 -109
  412. package/src/adapter/repositories/FileSystemKevReportWatermarkRepository.test.ts +0 -284
  413. package/src/adapter/repositories/FileSystemKevReportWatermarkRepository.ts +0 -128
  414. package/src/adapter/repositories/FileSystemSessionAssistantTurnsRepository.test.ts +0 -138
  415. package/src/adapter/repositories/FileSystemSessionAssistantTurnsRepository.ts +0 -113
  416. package/src/adapter/repositories/FileSystemSessionDegenerationCooldownStateRepository.test.ts +0 -121
  417. package/src/adapter/repositories/FileSystemSessionDegenerationCooldownStateRepository.ts +0 -103
  418. package/src/adapter/repositories/FileSystemSessionOutputActivityRepository.test.ts +0 -360
  419. package/src/adapter/repositories/FileSystemSessionOutputActivityRepository.ts +0 -77
  420. package/src/adapter/repositories/FileSystemSessionRecordReader.test.ts +0 -59
  421. package/src/adapter/repositories/FileSystemSessionRecordReader.ts +0 -31
  422. package/src/adapter/repositories/FileSystemSilentSessionCandidateStateRepository.test.ts +0 -287
  423. package/src/adapter/repositories/FileSystemSilentSessionCandidateStateRepository.ts +0 -125
  424. package/src/adapter/repositories/FileSystemSilentSessionHubTaskStatusCacheRepository.test.ts +0 -285
  425. package/src/adapter/repositories/FileSystemSilentSessionHubTaskStatusCacheRepository.ts +0 -119
  426. package/src/adapter/repositories/FileSystemSubAgentLivenessResolver.test.ts +0 -237
  427. package/src/adapter/repositories/FileSystemSubAgentLivenessResolver.ts +0 -57
  428. package/src/adapter/repositories/FileSystemSubAgentSilentSecondsResolver.test.ts +0 -56
  429. package/src/adapter/repositories/FileSystemSubAgentSilentSecondsResolver.ts +0 -32
  430. package/src/adapter/repositories/FileSystemSubAgentTranscriptDirectoryResolver.test.ts +0 -68
  431. package/src/adapter/repositories/FileSystemSubAgentTranscriptDirectoryResolver.ts +0 -23
  432. package/src/adapter/repositories/GitHubIssueCheckpointRepository.test.ts +0 -51
  433. package/src/adapter/repositories/GitHubIssueCheckpointRepository.ts +0 -47
  434. package/src/adapter/repositories/GitHubIssueCommentRepository.test.ts +0 -278
  435. package/src/adapter/repositories/GitHubIssueCommentRepository.ts +0 -109
  436. package/src/adapter/repositories/GoogleSpreadsheetRepository.integration.test.ts +0 -265
  437. package/src/adapter/repositories/GoogleSpreadsheetRepository.test.ts +0 -431
  438. package/src/adapter/repositories/GoogleSpreadsheetRepository.ts +0 -268
  439. package/src/adapter/repositories/GraphqlProjectRepository.diskCache.test.ts +0 -272
  440. package/src/adapter/repositories/GraphqlProjectRepository.errorHandling.test.ts +0 -111
  441. package/src/adapter/repositories/GraphqlProjectRepository.fetchProjectId.test.ts +0 -254
  442. package/src/adapter/repositories/GraphqlProjectRepository.test.ts +0 -209
  443. package/src/adapter/repositories/GraphqlProjectRepository.ts +0 -707
  444. package/src/adapter/repositories/GraphqlProjectRepositoryProjectLocation.test.ts +0 -297
  445. package/src/adapter/repositories/KyHttpRepository.ts +0 -8
  446. package/src/adapter/repositories/KySlackRepository.test.ts +0 -172
  447. package/src/adapter/repositories/KySlackRepository.ts +0 -298
  448. package/src/adapter/repositories/LocalCommandIssueAttachmentRepository.test.ts +0 -193
  449. package/src/adapter/repositories/LocalCommandIssueAttachmentRepository.ts +0 -106
  450. package/src/adapter/repositories/LocalProcessLiveSessionProcessSnapshotProvider.test.ts +0 -190
  451. package/src/adapter/repositories/LocalProcessLiveSessionProcessSnapshotProvider.ts +0 -102
  452. package/src/adapter/repositories/LocalStorageCacheRepository.test.ts +0 -265
  453. package/src/adapter/repositories/LocalStorageCacheRepository.ts +0 -79
  454. package/src/adapter/repositories/LocalStorageRepository.integration.test.ts +0 -142
  455. package/src/adapter/repositories/LocalStorageRepository.test.ts +0 -173
  456. package/src/adapter/repositories/LocalStorageRepository.ts +0 -30
  457. package/src/adapter/repositories/NoUnansweredOwnerCallStatusProvider.test.ts +0 -15
  458. package/src/adapter/repositories/NoUnansweredOwnerCallStatusProvider.ts +0 -9
  459. package/src/adapter/repositories/NodeLocalCommandRunner.largeOutput.test.ts +0 -16
  460. package/src/adapter/repositories/NodeLocalCommandRunner.test.ts +0 -118
  461. package/src/adapter/repositories/NodeLocalCommandRunner.ts +0 -66
  462. package/src/adapter/repositories/NodeProcessSignalRepository.test.ts +0 -82
  463. package/src/adapter/repositories/NodeProcessSignalRepository.ts +0 -47
  464. package/src/adapter/repositories/NodeSubAgentProcessLister.test.ts +0 -44
  465. package/src/adapter/repositories/NodeSubAgentProcessLister.ts +0 -40
  466. package/src/adapter/repositories/NodeTmuxSessionRepository.test.ts +0 -546
  467. package/src/adapter/repositories/NodeTmuxSessionRepository.ts +0 -245
  468. package/src/adapter/repositories/OauthAPIClaudeRepository.ts +0 -279
  469. package/src/adapter/repositories/OauthAPIProxyClaudeRepository.test.ts +0 -166
  470. package/src/adapter/repositories/OauthAPIProxyClaudeRepository.ts +0 -32
  471. package/src/adapter/repositories/OauthProxyClaudeRepository.test.ts +0 -428
  472. package/src/adapter/repositories/OauthProxyClaudeRepository.ts +0 -124
  473. package/src/adapter/repositories/ProcClaudeHandoverSessionRepository.test.ts +0 -242
  474. package/src/adapter/repositories/ProcClaudeHandoverSessionRepository.ts +0 -261
  475. package/src/adapter/repositories/ProcClaudeInteractiveSessionRepository.test.ts +0 -184
  476. package/src/adapter/repositories/ProcClaudeInteractiveSessionRepository.ts +0 -138
  477. package/src/adapter/repositories/ProcClaudeLiveSessionRepository.test.ts +0 -208
  478. package/src/adapter/repositories/ProcClaudeLiveSessionRepository.ts +0 -130
  479. package/src/adapter/repositories/ProcFsProcessEnvironReader.test.ts +0 -60
  480. package/src/adapter/repositories/ProcFsProcessEnvironReader.ts +0 -38
  481. package/src/adapter/repositories/ProcHostMetricsRepository.test.ts +0 -207
  482. package/src/adapter/repositories/ProcHostMetricsRepository.ts +0 -181
  483. package/src/adapter/repositories/ProcTakeOwnershipSpawnRepository.test.ts +0 -231
  484. package/src/adapter/repositories/ProcTakeOwnershipSpawnRepository.ts +0 -134
  485. package/src/adapter/repositories/ProcessListSessionSubAgentActivityRepository.test.ts +0 -121
  486. package/src/adapter/repositories/ProcessListSessionSubAgentActivityRepository.ts +0 -58
  487. package/src/adapter/repositories/ProjectIssuesCacheRepository.test.ts +0 -218
  488. package/src/adapter/repositories/ProjectIssuesCacheRepository.ts +0 -141
  489. package/src/adapter/repositories/ProxyClaudeTokenUsageRepository.test.ts +0 -1195
  490. package/src/adapter/repositories/ProxyClaudeTokenUsageRepository.ts +0 -185
  491. package/src/adapter/repositories/ProxyRateLimitCacheRepository.test.ts +0 -116
  492. package/src/adapter/repositories/ProxyRateLimitCacheRepository.ts +0 -67
  493. package/src/adapter/repositories/RateLimitSnapshotRepository.ts +0 -50
  494. package/src/adapter/repositories/RealSleeper.test.ts +0 -19
  495. package/src/adapter/repositories/RealSleeper.ts +0 -12
  496. package/src/adapter/repositories/RestProjectRepository.test.ts +0 -277
  497. package/src/adapter/repositories/RestProjectRepository.ts +0 -129
  498. package/src/adapter/repositories/SqliteClaudeMessageResponseRepository.test.ts +0 -313
  499. package/src/adapter/repositories/SqliteClaudeMessageResponseRepository.ts +0 -164
  500. package/src/adapter/repositories/SystemDateRepository.test.ts +0 -60
  501. package/src/adapter/repositories/SystemDateRepository.ts +0 -34
  502. package/src/adapter/repositories/TmuxSilentSessionNotificationRepository.test.ts +0 -253
  503. package/src/adapter/repositories/TmuxSilentSessionNotificationRepository.ts +0 -158
  504. package/src/adapter/repositories/TranscriptOwnerCallStatusProvider.queuedReply.test.ts +0 -171
  505. package/src/adapter/repositories/TranscriptOwnerCallStatusProvider.singleScan.test.ts +0 -86
  506. package/src/adapter/repositories/TranscriptOwnerCallStatusProvider.test.ts +0 -996
  507. package/src/adapter/repositories/TranscriptOwnerCallStatusProvider.ts +0 -396
  508. package/src/adapter/repositories/TranscriptRefusalTailStatusProvider.test.ts +0 -208
  509. package/src/adapter/repositories/TranscriptRefusalTailStatusProvider.ts +0 -80
  510. package/src/adapter/repositories/TranscriptSessionSubAgentActivityRepository.test.ts +0 -1674
  511. package/src/adapter/repositories/TranscriptSessionSubAgentActivityRepository.ts +0 -459
  512. package/src/adapter/repositories/clSessionScopeUnitName.test.ts +0 -76
  513. package/src/adapter/repositories/clSessionScopeUnitName.ts +0 -5
  514. package/src/adapter/repositories/clSessionScopeUnitNameFromCgroupContent.test.ts +0 -34
  515. package/src/adapter/repositories/clSessionScopeUnitNameFromCgroupContent.ts +0 -8
  516. package/src/adapter/repositories/githubGraphqlClient.test.ts +0 -479
  517. package/src/adapter/repositories/githubGraphqlClient.ts +0 -243
  518. package/src/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.test.ts +0 -5182
  519. package/src/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.ts +0 -3303
  520. package/src/adapter/repositories/issue/ApiV3IssueRepository.test.ts +0 -46
  521. package/src/adapter/repositories/issue/ApiV3IssueRepository.ts +0 -88
  522. package/src/adapter/repositories/issue/GraphqlProjectItemRepository.test.ts +0 -1776
  523. package/src/adapter/repositories/issue/GraphqlProjectItemRepository.ts +0 -1552
  524. package/src/adapter/repositories/issue/RestIssueRepository.test.ts +0 -393
  525. package/src/adapter/repositories/issue/RestIssueRepository.ts +0 -252
  526. package/src/adapter/repositories/issue/gitHubRawUrl.test.ts +0 -29
  527. package/src/adapter/repositories/issue/gitHubRawUrl.ts +0 -30
  528. package/src/adapter/repositories/issue/githubRateLimitRetry.test.ts +0 -167
  529. package/src/adapter/repositories/issue/githubRateLimitRetry.ts +0 -109
  530. package/src/adapter/repositories/localStorageCacheDirectory.test.ts +0 -81
  531. package/src/adapter/repositories/localStorageCacheDirectory.ts +0 -13
  532. package/src/adapter/repositories/projectFieldDefinition.test.ts +0 -191
  533. package/src/adapter/repositories/projectFieldDefinition.ts +0 -144
  534. package/src/adapter/repositories/utils.test.ts +0 -55
  535. package/src/adapter/repositories/utils.ts +0 -45
  536. package/src/domain/entities/ClaudeHandoverSession.ts +0 -12
  537. package/src/domain/entities/ClaudeMessageResponse.ts +0 -31
  538. package/src/domain/entities/ClaudeTokenUsage.ts +0 -17
  539. package/src/domain/entities/ClaudeWindowUsage.ts +0 -5
  540. package/src/domain/entities/Comment.ts +0 -5
  541. package/src/domain/entities/InteractiveLiveSession.ts +0 -6
  542. package/src/domain/entities/Issue.ts +0 -31
  543. package/src/domain/entities/KevReportWatermark.ts +0 -4
  544. package/src/domain/entities/LiveSessionActivitySnapshot.ts +0 -14
  545. package/src/domain/entities/LiveSessionOutputActivity.ts +0 -4
  546. package/src/domain/entities/LiveSessionProcessSnapshot.ts +0 -18
  547. package/src/domain/entities/LiveTmuxSession.ts +0 -4
  548. package/src/domain/entities/Member.ts +0 -3
  549. package/src/domain/entities/OutputDegeneration.ts +0 -10
  550. package/src/domain/entities/Project.ts +0 -88
  551. package/src/domain/entities/ProjectField.ts +0 -3
  552. package/src/domain/entities/ProjectFieldName.ts +0 -2
  553. package/src/domain/entities/ProjectFieldSingleSelect.ts +0 -8
  554. package/src/domain/entities/ProjectFieldSingleSelectOption.ts +0 -8
  555. package/src/domain/entities/RequiredProjectField.ts +0 -68
  556. package/src/domain/entities/SearchedIssue.ts +0 -11
  557. package/src/domain/entities/StoryObjectMap.ts +0 -12
  558. package/src/domain/entities/TokenExhaustionHandoverState.ts +0 -8
  559. package/src/domain/entities/WorkflowStatus.ts +0 -66
  560. package/src/domain/usecases/ActionAnnouncementUseCase.test.ts +0 -91
  561. package/src/domain/usecases/ActionAnnouncementUseCase.ts +0 -78
  562. package/src/domain/usecases/AgentDesignationLabelAdoptUseCase.test.ts +0 -222
  563. package/src/domain/usecases/AgentDesignationLabelAdoptUseCase.ts +0 -76
  564. package/src/domain/usecases/AnalyzeProblemByIssueUseCase.test.ts +0 -118
  565. package/src/domain/usecases/AnalyzeProblemByIssueUseCase.ts +0 -112
  566. package/src/domain/usecases/AnalyzeStoriesUseCase.test.ts +0 -82
  567. package/src/domain/usecases/AnalyzeStoriesUseCase.ts +0 -296
  568. package/src/domain/usecases/AssignNoAssigneeIssueToManagerUseCase.test.ts +0 -578
  569. package/src/domain/usecases/AssignNoAssigneeIssueToManagerUseCase.ts +0 -137
  570. package/src/domain/usecases/CONTRIBUTING_FOR_TEST.md +0 -259
  571. package/src/domain/usecases/ChangeStatusByStoryColorUseCase.test.ts +0 -437
  572. package/src/domain/usecases/ChangeStatusByStoryColorUseCase.ts +0 -80
  573. package/src/domain/usecases/ChangeTargetPullRequestApprover.test.ts +0 -248
  574. package/src/domain/usecases/ChangeTargetPullRequestApprover.ts +0 -69
  575. package/src/domain/usecases/CheckIssueReviewReadinessUseCase.test.ts +0 -411
  576. package/src/domain/usecases/CheckIssueReviewReadinessUseCase.ts +0 -142
  577. package/src/domain/usecases/ClearDependedIssueURLUseCase.test.ts +0 -1120
  578. package/src/domain/usecases/ClearDependedIssueURLUseCase.ts +0 -161
  579. package/src/domain/usecases/ClearPastNextActionDateHourUseCase.test.ts +0 -345
  580. package/src/domain/usecases/ClearPastNextActionDateHourUseCase.ts +0 -81
  581. package/src/domain/usecases/CliErrorReportUseCase.test.ts +0 -162
  582. package/src/domain/usecases/CliErrorReportUseCase.ts +0 -72
  583. package/src/domain/usecases/ConflictedIssueRevertUseCase.test.ts +0 -886
  584. package/src/domain/usecases/ConflictedIssueRevertUseCase.ts +0 -146
  585. package/src/domain/usecases/CreateEstimationIssueUseCase.test.ts +0 -227
  586. package/src/domain/usecases/CreateEstimationIssueUseCase.ts +0 -205
  587. package/src/domain/usecases/CreateNewStoryByLabelUseCase.test.ts +0 -1084
  588. package/src/domain/usecases/CreateNewStoryByLabelUseCase.ts +0 -146
  589. package/src/domain/usecases/DailySecurityScanUseCase.test.ts +0 -1709
  590. package/src/domain/usecases/DailySecurityScanUseCase.ts +0 -476
  591. package/src/domain/usecases/DefaultSilentSessionMessageComposer.test.ts +0 -533
  592. package/src/domain/usecases/DefaultSilentSessionMessageComposer.ts +0 -122
  593. package/src/domain/usecases/GetStoryObjectMapUseCase.test.ts +0 -330
  594. package/src/domain/usecases/GetStoryObjectMapUseCase.ts +0 -79
  595. package/src/domain/usecases/HandleScheduledEventUseCase.test.ts +0 -1913
  596. package/src/domain/usecases/HandleScheduledEventUseCase.ts +0 -846
  597. package/src/domain/usecases/InTmuxByHumanSessionTokenCountUseCase.test.ts +0 -175
  598. package/src/domain/usecases/InTmuxByHumanSessionTokenCountUseCase.ts +0 -63
  599. package/src/domain/usecases/IssueNoStatusUpdateUseCase.test.ts +0 -165
  600. package/src/domain/usecases/IssueNoStatusUpdateUseCase.ts +0 -44
  601. package/src/domain/usecases/IssueRejectionEvaluator.test.ts +0 -1137
  602. package/src/domain/usecases/IssueRejectionEvaluator.ts +0 -340
  603. package/src/domain/usecases/LiveSessionOauthTokenSelectUseCase.test.ts +0 -679
  604. package/src/domain/usecases/LiveSessionOauthTokenSelectUseCase.ts +0 -178
  605. package/src/domain/usecases/NotifyFinishedIssuePreparationUseCase.test.ts +0 -5441
  606. package/src/domain/usecases/NotifyFinishedIssuePreparationUseCase.ts +0 -891
  607. package/src/domain/usecases/NotifySilentLiveSessionsUseCase.test.ts +0 -2069
  608. package/src/domain/usecases/NotifySilentLiveSessionsUseCase.ts +0 -489
  609. package/src/domain/usecases/OauthTokenSelectUseCase.test.ts +0 -662
  610. package/src/domain/usecases/OauthTokenSelectUseCase.ts +0 -274
  611. package/src/domain/usecases/OutputDegenerationDetector.test.ts +0 -167
  612. package/src/domain/usecases/OutputDegenerationDetector.ts +0 -169
  613. package/src/domain/usecases/ProjectRequiredFieldCreateUseCase.test.ts +0 -711
  614. package/src/domain/usecases/ProjectRequiredFieldCreateUseCase.ts +0 -146
  615. package/src/domain/usecases/QualityCheckAdvanceUseCase.test.ts +0 -421
  616. package/src/domain/usecases/QualityCheckAdvanceUseCase.ts +0 -76
  617. package/src/domain/usecases/ReopenedDoneIssueRevertUseCase.test.ts +0 -188
  618. package/src/domain/usecases/ReopenedDoneIssueRevertUseCase.ts +0 -57
  619. package/src/domain/usecases/ResolveInteractiveLiveSessionsUseCase.test.ts +0 -303
  620. package/src/domain/usecases/ResolveInteractiveLiveSessionsUseCase.ts +0 -155
  621. package/src/domain/usecases/RevertNotReadyReviewQueueIssueUseCase.test.ts +0 -1838
  622. package/src/domain/usecases/RevertNotReadyReviewQueueIssueUseCase.ts +0 -221
  623. package/src/domain/usecases/RevertOrphanedPreparationUseCase.test.ts +0 -2392
  624. package/src/domain/usecases/RevertOrphanedPreparationUseCase.ts +0 -499
  625. package/src/domain/usecases/SessionOutputDegenerationRecoveryUseCase.test.ts +0 -224
  626. package/src/domain/usecases/SessionOutputDegenerationRecoveryUseCase.ts +0 -142
  627. package/src/domain/usecases/SetDependedIssueUrlForOpenTaskPRsUseCase.test.ts +0 -360
  628. package/src/domain/usecases/SetDependedIssueUrlForOpenTaskPRsUseCase.ts +0 -70
  629. package/src/domain/usecases/SetNoStoryIssueToStoryUseCase.test.ts +0 -400
  630. package/src/domain/usecases/SetNoStoryIssueToStoryUseCase.ts +0 -48
  631. package/src/domain/usecases/SetWorkflowManagementIssueToStoryUseCase.test.ts +0 -696
  632. package/src/domain/usecases/SetWorkflowManagementIssueToStoryUseCase.ts +0 -201
  633. package/src/domain/usecases/SetupTowerDefenceProjectUseCase.test.ts +0 -1363
  634. package/src/domain/usecases/SetupTowerDefenceProjectUseCase.ts +0 -215
  635. package/src/domain/usecases/StaleTaskPullRequestCloseUseCase.test.ts +0 -170
  636. package/src/domain/usecases/StaleTaskPullRequestCloseUseCase.ts +0 -38
  637. package/src/domain/usecases/StaleTmuxSessionKillUseCase.test.ts +0 -283
  638. package/src/domain/usecases/StaleTmuxSessionKillUseCase.ts +0 -98
  639. package/src/domain/usecases/StartPreparationUseCase.test.ts +0 -7822
  640. package/src/domain/usecases/StartPreparationUseCase.ts +0 -876
  641. package/src/domain/usecases/SubscriptionDisabledIssueUseCase.test.ts +0 -200
  642. package/src/domain/usecases/SubscriptionDisabledIssueUseCase.ts +0 -74
  643. package/src/domain/usecases/TokenExhaustionHandoverUseCase.test.ts +0 -838
  644. package/src/domain/usecases/TokenExhaustionHandoverUseCase.ts +0 -439
  645. package/src/domain/usecases/UpdateIssueStatusByLabelUseCase.test.ts +0 -306
  646. package/src/domain/usecases/UpdateIssueStatusByLabelUseCase.ts +0 -64
  647. package/src/domain/usecases/UpdateRateLimitCacheUseCase.test.ts +0 -178
  648. package/src/domain/usecases/UpdateRateLimitCacheUseCase.ts +0 -22
  649. package/src/domain/usecases/adapter-interfaces/ClaudeHandoverSessionRepository.ts +0 -5
  650. package/src/domain/usecases/adapter-interfaces/ClaudeInteractiveSessionRepository.ts +0 -9
  651. package/src/domain/usecases/adapter-interfaces/ClaudeLiveSessionRepository.ts +0 -8
  652. package/src/domain/usecases/adapter-interfaces/ClaudeMessageResponseRepository.ts +0 -5
  653. package/src/domain/usecases/adapter-interfaces/ClaudeRepository.ts +0 -6
  654. package/src/domain/usecases/adapter-interfaces/ClaudeTokenUsageRepository.ts +0 -8
  655. package/src/domain/usecases/adapter-interfaces/ConsoleTabsRepository.ts +0 -12
  656. package/src/domain/usecases/adapter-interfaces/CopilotRepository.ts +0 -3
  657. package/src/domain/usecases/adapter-interfaces/DateRepository.ts +0 -7
  658. package/src/domain/usecases/adapter-interfaces/GitHubGraphqlRateLimitRepository.ts +0 -3
  659. package/src/domain/usecases/adapter-interfaces/HttpRepository.ts +0 -3
  660. package/src/domain/usecases/adapter-interfaces/InteractiveLiveSessionTranscriptResolver.ts +0 -7
  661. package/src/domain/usecases/adapter-interfaces/IssueAttachmentRepository.ts +0 -9
  662. package/src/domain/usecases/adapter-interfaces/IssueCheckpointRepository.ts +0 -3
  663. package/src/domain/usecases/adapter-interfaces/IssueCommentRepository.ts +0 -7
  664. package/src/domain/usecases/adapter-interfaces/IssueRepository.ts +0 -229
  665. package/src/domain/usecases/adapter-interfaces/KevReportWatermarkRepository.ts +0 -11
  666. package/src/domain/usecases/adapter-interfaces/LiveSessionProcessSnapshotProvider.ts +0 -5
  667. package/src/domain/usecases/adapter-interfaces/LocalCommandRunner.ts +0 -16
  668. package/src/domain/usecases/adapter-interfaces/OwnerCallStatusProvider.ts +0 -5
  669. package/src/domain/usecases/adapter-interfaces/ProcessEnvironReader.ts +0 -3
  670. package/src/domain/usecases/adapter-interfaces/ProcessSignalRepository.ts +0 -5
  671. package/src/domain/usecases/adapter-interfaces/ProjectRepository.ts +0 -29
  672. package/src/domain/usecases/adapter-interfaces/RateLimitCacheRepository.ts +0 -10
  673. package/src/domain/usecases/adapter-interfaces/RefusalTailStatusProvider.ts +0 -5
  674. package/src/domain/usecases/adapter-interfaces/SessionAssistantTurnsRepository.ts +0 -6
  675. package/src/domain/usecases/adapter-interfaces/SessionDegenerationCooldownStateRepository.ts +0 -4
  676. package/src/domain/usecases/adapter-interfaces/SessionOutputActivityRepository.ts +0 -7
  677. package/src/domain/usecases/adapter-interfaces/SessionRecordReader.ts +0 -3
  678. package/src/domain/usecases/adapter-interfaces/SessionSubAgentActivityRepository.ts +0 -8
  679. package/src/domain/usecases/adapter-interfaces/SilentSessionCandidateStateRepository.ts +0 -10
  680. package/src/domain/usecases/adapter-interfaces/SilentSessionHubTaskStatusCacheRepository.ts +0 -20
  681. package/src/domain/usecases/adapter-interfaces/SilentSessionMessageComposer.ts +0 -18
  682. package/src/domain/usecases/adapter-interfaces/SilentSessionNotificationRepository.ts +0 -6
  683. package/src/domain/usecases/adapter-interfaces/SlackRepository.ts +0 -20
  684. package/src/domain/usecases/adapter-interfaces/Sleeper.ts +0 -3
  685. package/src/domain/usecases/adapter-interfaces/SpreadsheetRepository.ts +0 -18
  686. package/src/domain/usecases/adapter-interfaces/StatusDefaultRepository.ts +0 -5
  687. package/src/domain/usecases/adapter-interfaces/SubAgentLivenessResolver.ts +0 -6
  688. package/src/domain/usecases/adapter-interfaces/SubAgentProcessLister.ts +0 -8
  689. package/src/domain/usecases/adapter-interfaces/SubAgentSilentSecondsResolver.ts +0 -3
  690. package/src/domain/usecases/adapter-interfaces/SubAgentTranscriptDirectoryResolver.ts +0 -6
  691. package/src/domain/usecases/adapter-interfaces/TakeOwnershipSpawnRepository.ts +0 -9
  692. package/src/domain/usecases/adapter-interfaces/TmuxSessionRepository.ts +0 -20
  693. package/src/domain/usecases/adapter-interfaces/TokenExhaustionHandoverStateRepository.ts +0 -6
  694. package/src/domain/usecases/adapter-interfaces/TokenRateLimitSnapshotRepository.ts +0 -22
  695. package/src/domain/usecases/adapter-interfaces/WebhookRepository.ts +0 -3
  696. package/src/domain/usecases/agentReportPrefix.ts +0 -1
  697. package/src/domain/usecases/autoStatusCheckComments.ts +0 -1
  698. package/src/domain/usecases/console/.gitkeep +0 -0
  699. package/src/domain/usecases/console/GenerateConsoleListsUseCase.test.ts +0 -887
  700. package/src/domain/usecases/console/GenerateConsoleListsUseCase.ts +0 -368
  701. package/src/domain/usecases/dashboard/ComposeDashboardUseCase.test.ts +0 -654
  702. package/src/domain/usecases/dashboard/ComposeDashboardUseCase.ts +0 -318
  703. package/src/domain/usecases/dashboard/DashboardProjectCode.test.ts +0 -34
  704. package/src/domain/usecases/dashboard/DashboardProjectCode.ts +0 -27
  705. package/src/domain/usecases/dashboard/GenerateDashboardRowUseCase.test.ts +0 -160
  706. package/src/domain/usecases/dashboard/GenerateDashboardRowUseCase.ts +0 -69
  707. package/src/domain/usecases/dashboard/GenerateTokenStatusUseCase.test.ts +0 -362
  708. package/src/domain/usecases/dashboard/GenerateTokenStatusUseCase.ts +0 -234
  709. package/src/domain/usecases/ensureAgentOptionAndGetId.ts +0 -47
  710. package/src/domain/usecases/extractFencedJsonBlocks.test.ts +0 -40
  711. package/src/domain/usecases/extractFencedJsonBlocks.ts +0 -21
  712. package/src/domain/usecases/extractNextStepAgent.test.ts +0 -58
  713. package/src/domain/usecases/extractNextStepAgent.ts +0 -18
  714. package/src/domain/usecases/extractNextStepAgentFromComments.ts +0 -13
  715. package/src/domain/usecases/findLastAgentReport.test.ts +0 -121
  716. package/src/domain/usecases/findLastAgentReport.ts +0 -14
  717. package/src/domain/usecases/intmux/GenerateInTmuxByHumanDataUseCase.test.ts +0 -339
  718. package/src/domain/usecases/intmux/GenerateInTmuxByHumanDataUseCase.ts +0 -188
  719. package/src/domain/usecases/intmux/InTmuxByHumanSessionReconcileUseCase.test.ts +0 -389
  720. package/src/domain/usecases/intmux/InTmuxByHumanSessionReconcileUseCase.ts +0 -103
  721. package/src/domain/usecases/intmux/OwnerCallFile.test.ts +0 -184
  722. package/src/domain/usecases/intmux/OwnerCallFile.ts +0 -80
  723. package/src/domain/usecases/isAgentReportBody.test.ts +0 -108
  724. package/src/domain/usecases/isAgentReportBody.ts +0 -52
  725. package/src/domain/usecases/isAuthorAuthorizedForAutoStatusCheck.ts +0 -12
  726. package/src/domain/usecases/isHumanComment.test.ts +0 -77
  727. package/src/domain/usecases/isHumanComment.ts +0 -23
  728. package/src/domain/usecases/isRecord.ts +0 -2
  729. package/src/domain/usecases/issueReactivationTriggerIsPending.test.ts +0 -541
  730. package/src/domain/usecases/issueReactivationTriggerIsPending.ts +0 -25
  731. package/src/domain/usecases/nextStepAgentDispatchRepeatedMessage.ts +0 -2
  732. package/src/domain/usecases/normalizeReportBody.test.ts +0 -24
  733. package/src/domain/usecases/normalizeReportBody.ts +0 -2
  734. package/src/domain/usecases/resolveAllowedIssueAuthors.test.ts +0 -51
  735. package/src/domain/usecases/resolveAllowedIssueAuthors.ts +0 -6
  736. package/src/domain/usecases/resolveLabelsAsLlmAgentName.test.ts +0 -42
  737. package/src/domain/usecases/resolveLabelsAsLlmAgentName.ts +0 -6
  738. package/src/domain/usecases/resolveLabelsNotRequiringPullRequest.test.ts +0 -33
  739. package/src/domain/usecases/resolveLabelsNotRequiringPullRequest.ts +0 -7
  740. package/src/domain/usecases/resolveNextStepAgentDispatchRepetition.test.ts +0 -337
  741. package/src/domain/usecases/resolveNextStepAgentDispatchRepetition.ts +0 -133
  742. package/src/domain/usecases/silentSessionReminderSentinel.ts +0 -9
  743. package/src/domain/usecases/triagerAgentName.ts +0 -4
  744. package/src/domain/usecases/utils.test.ts +0 -35
  745. package/src/domain/usecases/utils.ts +0 -6
  746. package/src/index.test.ts +0 -8
  747. package/src/index.ts +0 -10
  748. package/src/releaseConfig.test.ts +0 -40
  749. package/tsconfig.build.json +0 -18
  750. package/tsconfig.json +0 -25
  751. package/types/adapter/entry-points/handlers/resolveUnansweredOwnerCalls.d.ts +0 -13
  752. package/types/adapter/entry-points/handlers/resolveUnansweredOwnerCalls.d.ts.map +0 -1
  753. package/types/adapter/repositories/FileSystemSilentSessionNotifiedStateRepository.d.ts +0 -16
  754. package/types/adapter/repositories/FileSystemSilentSessionNotifiedStateRepository.d.ts.map +0 -1
  755. package/types/adapter/repositories/issue/issueTimelineUtils.d.ts +0 -12
  756. package/types/adapter/repositories/issue/issueTimelineUtils.d.ts.map +0 -1
  757. package/types/domain/entities/UnansweredOwnerCall.d.ts +0 -5
  758. package/types/domain/entities/UnansweredOwnerCall.d.ts.map +0 -1
  759. package/types/domain/entities/WorkingTime.d.ts +0 -8
  760. package/types/domain/entities/WorkingTime.d.ts.map +0 -1
  761. package/types/domain/usecases/ChangeStatusLongInReviewIssueUseCase.d.ts +0 -17
  762. package/types/domain/usecases/ChangeStatusLongInReviewIssueUseCase.d.ts.map +0 -1
  763. package/types/domain/usecases/ConvertCheckboxToIssueInStoryIssueUseCase.d.ts +0 -23
  764. package/types/domain/usecases/ConvertCheckboxToIssueInStoryIssueUseCase.d.ts.map +0 -1
  765. package/types/domain/usecases/GenerateWorkingTimeReportUseCase.d.ts +0 -50
  766. package/types/domain/usecases/GenerateWorkingTimeReportUseCase.d.ts.map +0 -1
  767. package/types/domain/usecases/TriagerApprovalDispatchUseCase.d.ts +0 -15
  768. package/types/domain/usecases/TriagerApprovalDispatchUseCase.d.ts.map +0 -1
  769. package/types/domain/usecases/adapter-interfaces/SilentSessionNotifiedStateRepository.d.ts +0 -11
  770. package/types/domain/usecases/adapter-interfaces/SilentSessionNotifiedStateRepository.d.ts.map +0 -1
  771. package/types/domain/usecases/awaitingOwnerApprovalMessage.d.ts +0 -3
  772. package/types/domain/usecases/awaitingOwnerApprovalMessage.d.ts.map +0 -1
  773. package/types/domain/usecases/findLastAgentDeclaringReport.d.ts +0 -5
  774. package/types/domain/usecases/findLastAgentDeclaringReport.d.ts.map +0 -1
  775. package/types/domain/usecases/isPullRequestDeclaredUnnecessary.d.ts +0 -5
  776. package/types/domain/usecases/isPullRequestDeclaredUnnecessary.d.ts.map +0 -1
  777. package/types/domain/usecases/isWaitingForOwnerApproval.d.ts +0 -2
  778. package/types/domain/usecases/isWaitingForOwnerApproval.d.ts.map +0 -1
  779. package/types/domain/usecases/returnedToAwaitingWorkspaceMessage.d.ts +0 -3
  780. package/types/domain/usecases/returnedToAwaitingWorkspaceMessage.d.ts.map +0 -1
@@ -1,2837 +0,0 @@
1
- import * as fs from 'fs';
2
- import * as os from 'os';
3
- import * as path from 'path';
4
- import YAML from 'yaml';
5
-
6
- jest.mock('fs', () => {
7
- const actualFs: typeof fs = jest.requireActual('fs');
8
- return {
9
- ...actualFs,
10
- readFileSync: jest.fn(actualFs.readFileSync),
11
- existsSync: jest.fn(actualFs.existsSync),
12
- };
13
- });
14
- import {
15
- program,
16
- loadConfigFile,
17
- parseProjectReadmeConfig,
18
- mergeConfigs,
19
- fetchProjectReadme,
20
- runCliProgram,
21
- reportFatalErrorAndExit,
22
- } from './index';
23
- import { StartPreparationUseCase } from '../../../domain/usecases/StartPreparationUseCase';
24
- import { NotifyFinishedIssuePreparationUseCase } from '../../../domain/usecases/NotifyFinishedIssuePreparationUseCase';
25
- import { CheckIssueReviewReadinessUseCase } from '../../../domain/usecases/CheckIssueReviewReadinessUseCase';
26
- import { ownerCallFileRelativePath } from '../../../domain/usecases/intmux/OwnerCallFile';
27
- import { toTmuxSessionName } from '../../../domain/usecases/intmux/InTmuxByHumanSessionReconcileUseCase';
28
-
29
- jest.mock('../../../domain/usecases/StartPreparationUseCase');
30
- jest.mock('../../../domain/usecases/NotifyFinishedIssuePreparationUseCase');
31
- jest.mock('../../../domain/usecases/CheckIssueReviewReadinessUseCase');
32
- jest.mock('../../repositories/LocalStorageRepository', () => ({
33
- LocalStorageRepository: jest.fn().mockImplementation(() => ({})),
34
- }));
35
- jest.mock('../../repositories/LocalStorageCacheRepository', () => ({
36
- LocalStorageCacheRepository: jest.fn().mockImplementation(() => ({})),
37
- }));
38
- jest.mock('../../repositories/GraphqlProjectRepository', () => ({
39
- GraphqlProjectRepository: jest.fn().mockImplementation(() => ({
40
- findProjectIdByUrl: jest.fn().mockResolvedValue('PVT_kwHOtest456'),
41
- getProject: jest.fn().mockResolvedValue({
42
- id: 'PVT_kwHOtest456',
43
- url: 'https://github.com/orgs/test/projects/1',
44
- databaseId: 1,
45
- name: 'test-project',
46
- status: { name: 'Status', fieldId: 'statusField', statuses: [] },
47
- nextActionDate: null,
48
- nextActionHour: null,
49
- story: null,
50
- remainingEstimationMinutes: null,
51
- dependedIssueUrlSeparatedByComma: null,
52
- completionDate50PercentConfidence: null,
53
- }),
54
- })),
55
- }));
56
- jest.mock('../../repositories/issue/ApiV3IssueRepository', () => ({
57
- ApiV3IssueRepository: jest.fn().mockImplementation(() => ({})),
58
- }));
59
- jest.mock('../../repositories/issue/RestIssueRepository', () => ({
60
- RestIssueRepository: jest.fn().mockImplementation(() => ({})),
61
- }));
62
- jest.mock('../../repositories/issue/GraphqlProjectItemRepository', () => ({
63
- GraphqlProjectItemRepository: jest.fn().mockImplementation(() => ({})),
64
- }));
65
- jest.mock('../../repositories/issue/ApiV3CheerioRestIssueRepository', () => ({
66
- ApiV3CheerioRestIssueRepository: jest.fn().mockImplementation(() => ({
67
- getCachedProject: jest.fn().mockResolvedValue(null),
68
- })),
69
- }));
70
- const mockRunCommand = jest.fn<
71
- Promise<{ stdout: string; stderr: string; exitCode: number }>,
72
- [string, string[]]
73
- >();
74
- const mockSpawnInteractive = jest.fn<void, [string, string[]]>();
75
- jest.mock('../../repositories/NodeLocalCommandRunner', () => ({
76
- NodeLocalCommandRunner: jest.fn().mockImplementation(() => ({
77
- runCommand: mockRunCommand,
78
- spawnInteractive: mockSpawnInteractive,
79
- })),
80
- }));
81
- jest.mock('../../repositories/OauthAPIClaudeRepository', () => ({
82
- OauthAPIClaudeRepository: jest.fn().mockImplementation(() => ({
83
- getUsage: jest.fn(),
84
- isClaudeAvailable: jest.fn(),
85
- })),
86
- }));
87
- jest.mock('../../repositories/GitHubIssueCommentRepository', () => ({
88
- GitHubIssueCommentRepository: jest.fn().mockImplementation(() => ({})),
89
- }));
90
- jest.mock('../../repositories/FetchWebhookRepository', () => ({
91
- FetchWebhookRepository: jest.fn().mockImplementation(() => ({
92
- sendGetRequest: jest.fn(),
93
- })),
94
- }));
95
- const mockScheduleHandle = jest.fn().mockResolvedValue(null);
96
- jest.mock('../handlers/HandleScheduledEventUseCaseHandler', () => ({
97
- HandleScheduledEventUseCaseHandler: jest.fn().mockImplementation(() => ({
98
- handle: mockScheduleHandle,
99
- })),
100
- }));
101
- jest.mock('../console/ensureConsoleRunning', () => ({
102
- ensureConsoleRunning: jest.fn().mockResolvedValue(null),
103
- }));
104
- import * as ensureConsoleRunningModule from '../console/ensureConsoleRunning';
105
- import { GraphqlProjectRepository } from '../../repositories/GraphqlProjectRepository';
106
-
107
- import type { StartWebServerOptions } from '../console/webServer';
108
-
109
- const mockStartWebServer = jest
110
- .fn<Promise<unknown>, [StartWebServerOptions]>()
111
- .mockResolvedValue({
112
- close: jest.fn(),
113
- address: jest.fn().mockReturnValue({ port: 9980 }),
114
- });
115
- jest.mock('../console/webServer', () => {
116
- const actual: Record<string, unknown> = jest.requireActual(
117
- '../console/webServer',
118
- );
119
- return {
120
- ...actual,
121
- startWebServer: (options: StartWebServerOptions): Promise<unknown> =>
122
- mockStartWebServer(options),
123
- };
124
- });
125
-
126
- describe('CLI', () => {
127
- const originalEnv = process.env;
128
- const tmpDir = path.join(__dirname, '../../../../tmp/test-cli');
129
- const configFilePath = path.join(tmpDir, 'config.yml');
130
- const cacheHomeDir = path.join(tmpDir, 'cache-home');
131
-
132
- const defaultConfig = {
133
- projectUrl: 'https://github.com/orgs/test/projects/1',
134
- defaultAgentName: 'agent1',
135
- projectName: 'test-project',
136
- manager: 'test-manager',
137
- };
138
-
139
- const writeConfig = (config: Record<string, unknown>): void => {
140
- fs.writeFileSync(configFilePath, YAML.stringify(config));
141
- };
142
-
143
- const mockFetchReturningReadme = (readme: string | null): void => {
144
- const responseBody =
145
- readme === null
146
- ? { data: {} }
147
- : { data: { organization: { projectV2: { readme } } } };
148
- jest.spyOn(global, 'fetch').mockResolvedValue(
149
- new Response(JSON.stringify(responseBody), {
150
- status: 200,
151
- headers: { 'Content-Type': 'application/json' },
152
- }),
153
- );
154
- };
155
-
156
- beforeAll(() => {
157
- if (!fs.existsSync(tmpDir)) {
158
- fs.mkdirSync(tmpDir, { recursive: true });
159
- }
160
- });
161
-
162
- afterAll(() => {
163
- if (fs.existsSync(configFilePath)) {
164
- fs.unlinkSync(configFilePath);
165
- }
166
- if (fs.existsSync(cacheHomeDir)) {
167
- fs.rmSync(cacheHomeDir, { recursive: true, force: true });
168
- }
169
- if (fs.existsSync(tmpDir)) {
170
- fs.rmdirSync(tmpDir);
171
- }
172
- });
173
-
174
- beforeEach(() => {
175
- jest.clearAllMocks();
176
- mockFetchReturningReadme(null);
177
- mockRunCommand.mockResolvedValue({ stdout: '', stderr: '', exitCode: 0 });
178
- process.env = {
179
- ...originalEnv,
180
- GH_TOKEN: 'test-token',
181
- XDG_CACHE_HOME: cacheHomeDir,
182
- };
183
- writeConfig(defaultConfig);
184
- });
185
-
186
- afterEach(() => {
187
- process.env = originalEnv;
188
- });
189
-
190
- it('should export program', () => {
191
- expect(program).toBeDefined();
192
- });
193
-
194
- describe('schedule --inTmuxProjectOrder', () => {
195
- it('should pass the parsed, trimmed project order to the handler', async () => {
196
- await program.parseAsync([
197
- 'node',
198
- 'test',
199
- 'schedule',
200
- '-t',
201
- 'schedule',
202
- '-c',
203
- configFilePath,
204
- '--inTmuxProjectOrder',
205
- 'alpha, beta ,gamma',
206
- ]);
207
-
208
- expect(mockScheduleHandle).toHaveBeenCalledWith(
209
- configFilePath,
210
- undefined,
211
- ['alpha', 'beta', 'gamma'],
212
- );
213
- });
214
-
215
- it('should pass null when --inTmuxProjectOrder is omitted', async () => {
216
- await program.parseAsync([
217
- 'node',
218
- 'test',
219
- 'schedule',
220
- '-t',
221
- 'schedule',
222
- '-c',
223
- configFilePath,
224
- ]);
225
-
226
- expect(mockScheduleHandle).toHaveBeenCalledWith(
227
- configFilePath,
228
- undefined,
229
- null,
230
- );
231
- });
232
- });
233
-
234
- describe('loadConfigFile', () => {
235
- it('should load config from YAML file', () => {
236
- const config = {
237
- projectUrl: 'https://github.com/orgs/test/projects/1',
238
- defaultAgentName: 'agent1',
239
- defaultLlmModelName: 'claude-opus-4-5',
240
- defaultLlmAgentName: 'aw',
241
- maximumPreparingIssuesCount: 10,
242
- utilizationPercentageThreshold: 80,
243
- allowedIssueAuthors: 'user1,user2',
244
- thresholdForAutoReject: 5,
245
- workflowBlockerResolvedWebhookUrl: 'https://example.com/webhook',
246
- projectName: 'test-project',
247
- codexHomeCandidates: ['.codex-dev1', '.codex-main'],
248
- };
249
- writeConfig(config);
250
-
251
- const result = loadConfigFile(configFilePath);
252
-
253
- expect(result).toEqual(config);
254
- });
255
-
256
- it('should load codexHomeCandidates string array from config file', () => {
257
- const config = {
258
- ...defaultConfig,
259
- codexHomeCandidates: ['.codex-dev1', '.codex-dev2', '.codex-main'],
260
- };
261
- writeConfig(config);
262
-
263
- const result = loadConfigFile(configFilePath);
264
-
265
- expect(result.codexHomeCandidates).toEqual([
266
- '.codex-dev1',
267
- '.codex-dev2',
268
- '.codex-main',
269
- ]);
270
- });
271
-
272
- it('should return undefined codexHomeCandidates when not in config', () => {
273
- writeConfig(defaultConfig);
274
-
275
- const result = loadConfigFile(configFilePath);
276
-
277
- expect(result.codexHomeCandidates).toBeUndefined();
278
- });
279
-
280
- it('should return undefined codexHomeCandidates when array contains non-string items', () => {
281
- const config = {
282
- ...defaultConfig,
283
- codexHomeCandidates: ['.codex-dev1', 123],
284
- };
285
- writeConfig(config);
286
-
287
- const result = loadConfigFile(configFilePath);
288
-
289
- expect(result.codexHomeCandidates).toBeUndefined();
290
- });
291
-
292
- it('should return empty config for empty YAML', () => {
293
- fs.writeFileSync(configFilePath, '');
294
-
295
- const result = loadConfigFile(configFilePath);
296
-
297
- expect(result).toEqual({});
298
- });
299
-
300
- it('should ignore non-string values for string fields', () => {
301
- const config = {
302
- projectUrl: 123,
303
- defaultAgentName: true,
304
- };
305
- writeConfig(config);
306
-
307
- const result = loadConfigFile(configFilePath);
308
-
309
- expect(result.projectUrl).toBeUndefined();
310
- expect(result.defaultAgentName).toBeUndefined();
311
- });
312
-
313
- it('should ignore non-number values for number fields', () => {
314
- const config = {
315
- maximumPreparingIssuesCount: 'abc',
316
- utilizationPercentageThreshold: 'ghi',
317
- thresholdForAutoReject: 'jkl',
318
- };
319
- writeConfig(config);
320
-
321
- const result = loadConfigFile(configFilePath);
322
-
323
- expect(result.maximumPreparingIssuesCount).toBeUndefined();
324
- expect(result.utilizationPercentageThreshold).toBeUndefined();
325
- expect(result.thresholdForAutoReject).toBeUndefined();
326
- });
327
-
328
- it('should return empty config for array YAML', () => {
329
- fs.writeFileSync(configFilePath, '- item1\n- item2\n');
330
-
331
- const result = loadConfigFile(configFilePath);
332
-
333
- expect(result).toEqual({});
334
- });
335
-
336
- it('should exit with error when config file does not exist', () => {
337
- const nonExistentPath = path.join(tmpDir, 'nonexistent-config.yml');
338
- const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation();
339
- const processExitSpy = jest
340
- .spyOn(process, 'exit')
341
- .mockImplementation(() => {
342
- throw new Error('process.exit called');
343
- });
344
-
345
- expect(() => loadConfigFile(nonExistentPath)).toThrow(
346
- 'process.exit called',
347
- );
348
-
349
- expect(consoleErrorSpy).toHaveBeenCalledWith(
350
- expect.stringContaining(
351
- `Failed to load configuration file "${nonExistentPath}"`,
352
- ),
353
- );
354
- expect(processExitSpy).toHaveBeenCalledWith(1);
355
-
356
- consoleErrorSpy.mockRestore();
357
- processExitSpy.mockRestore();
358
- });
359
- });
360
-
361
- describe('parseProjectReadmeConfig', () => {
362
- it('should parse YAML from details/summary config section', () => {
363
- const readme = `# Project
364
- Some description
365
- <details>
366
- <summary>config</summary>
367
- defaultAgentName: 'readme-agent'
368
- defaultLlmModelName: 'claude-opus-4-5'
369
- </details>`;
370
-
371
- const result = parseProjectReadmeConfig(readme);
372
-
373
- expect(result.defaultAgentName).toBe('readme-agent');
374
- expect(result.defaultLlmModelName).toBe('claude-opus-4-5');
375
- });
376
-
377
- it('should return empty config when no details/summary section exists', () => {
378
- const readme = '# Project\nSome description without config section';
379
-
380
- const result = parseProjectReadmeConfig(readme);
381
-
382
- expect(result).toEqual({});
383
- });
384
-
385
- it('should return empty config when details section has empty content', () => {
386
- const readme = '<details>\n<summary>config</summary>\n</details>';
387
-
388
- const result = parseProjectReadmeConfig(readme);
389
-
390
- expect(result).toEqual({});
391
- });
392
-
393
- it('should return empty config when YAML content is not a record', () => {
394
- const readme =
395
- '<details>\n<summary>config</summary>\n- item1\n- item2\n</details>';
396
-
397
- const result = parseProjectReadmeConfig(readme);
398
-
399
- expect(result).toEqual({});
400
- });
401
-
402
- it('should handle invalid YAML gracefully', () => {
403
- const readme =
404
- '<details>\n<summary>config</summary>\ninvalid: [unclosed\n</details>';
405
- const consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation();
406
-
407
- const result = parseProjectReadmeConfig(readme);
408
-
409
- expect(result).toEqual({});
410
- expect(consoleWarnSpy).toHaveBeenCalledWith(
411
- 'Failed to parse YAML from project README config section',
412
- );
413
-
414
- consoleWarnSpy.mockRestore();
415
- });
416
-
417
- it('should parse number fields from README config', () => {
418
- const readme = `<details>
419
- <summary>config</summary>
420
- maximumPreparingIssuesCount: 15
421
- utilizationPercentageThreshold: 80
422
- thresholdForAutoReject: 5
423
- </details>`;
424
-
425
- const result = parseProjectReadmeConfig(readme);
426
-
427
- expect(result.maximumPreparingIssuesCount).toBe(15);
428
- expect(result.utilizationPercentageThreshold).toBe(80);
429
- expect(result.thresholdForAutoReject).toBe(5);
430
- });
431
-
432
- it('should be case-insensitive for the summary tag', () => {
433
- const readme = `<details>
434
- <SUMMARY>config</SUMMARY>
435
- defaultAgentName: 'case-test-agent'
436
- </details>`;
437
-
438
- const result = parseProjectReadmeConfig(readme);
439
-
440
- expect(result.defaultAgentName).toBe('case-test-agent');
441
- });
442
-
443
- it('should parse codexHomeCandidates string array from README config', () => {
444
- const readme = `<details>
445
- <summary>config</summary>
446
- codexHomeCandidates:
447
- - .codex-dev1
448
- - .codex-dev2
449
- - .codex-main
450
- </details>`;
451
-
452
- const result = parseProjectReadmeConfig(readme);
453
-
454
- expect(result.codexHomeCandidates).toEqual([
455
- '.codex-dev1',
456
- '.codex-dev2',
457
- '.codex-main',
458
- ]);
459
- });
460
-
461
- it('should parse changeTargetPathAliases map from README config', () => {
462
- const readme = `<details>
463
- <summary>config</summary>
464
- changeTargetPathAliases:
465
- adapters: src/domain/usecases/adapter-interfaces
466
- domain: src/domain
467
- </details>`;
468
-
469
- const result = parseProjectReadmeConfig(readme);
470
-
471
- expect(result.changeTargetPathAliases).toEqual({
472
- adapters: 'src/domain/usecases/adapter-interfaces',
473
- domain: 'src/domain',
474
- });
475
- });
476
-
477
- it('should not warn when only known top-level keys are present', () => {
478
- const readme = `<details>
479
- <summary>config</summary>
480
- defaultAgentName: 'agent'
481
- maximumPreparingIssuesCount: 5
482
- </details>`;
483
- const consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation();
484
-
485
- const result = parseProjectReadmeConfig(
486
- readme,
487
- 'https://github.com/orgs/example/projects/1',
488
- );
489
-
490
- expect(result.defaultAgentName).toBe('agent');
491
- expect(result.maximumPreparingIssuesCount).toBe(5);
492
- expect(consoleWarnSpy).not.toHaveBeenCalled();
493
-
494
- consoleWarnSpy.mockRestore();
495
- });
496
-
497
- it('should warn for each unknown top-level key with project URL', () => {
498
- const readme = `<details>
499
- <summary>config</summary>
500
- unknownOne: 'value-one'
501
- unknownTwo: 42
502
- </details>`;
503
- const consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation();
504
-
505
- parseProjectReadmeConfig(
506
- readme,
507
- 'https://github.com/orgs/example/projects/1',
508
- );
509
-
510
- expect(consoleWarnSpy).toHaveBeenCalledWith(
511
- 'Unknown key "unknownOne" in project README config section (project URL: https://github.com/orgs/example/projects/1)',
512
- );
513
- expect(consoleWarnSpy).toHaveBeenCalledWith(
514
- 'Unknown key "unknownTwo" in project README config section (project URL: https://github.com/orgs/example/projects/1)',
515
- );
516
- expect(consoleWarnSpy).toHaveBeenCalledTimes(2);
517
-
518
- consoleWarnSpy.mockRestore();
519
- });
520
-
521
- it('should warn only for unknown keys in a mixed config', () => {
522
- const readme = `<details>
523
- <summary>config</summary>
524
- defaultAgentName: 'agent'
525
- unexpectedKey: 'oops'
526
- </details>`;
527
- const consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation();
528
-
529
- const result = parseProjectReadmeConfig(
530
- readme,
531
- 'https://github.com/orgs/example/projects/2',
532
- );
533
-
534
- expect(result.defaultAgentName).toBe('agent');
535
- expect(consoleWarnSpy).toHaveBeenCalledTimes(1);
536
- expect(consoleWarnSpy).toHaveBeenCalledWith(
537
- 'Unknown key "unexpectedKey" in project README config section (project URL: https://github.com/orgs/example/projects/2)',
538
- );
539
-
540
- consoleWarnSpy.mockRestore();
541
- });
542
-
543
- it('should warn for a typo of a known key (maximumPreparingIssueCount missing trailing s)', () => {
544
- const readme = `<details>
545
- <summary>config</summary>
546
- maximumPreparingIssueCount: 9
547
- </details>`;
548
- const consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation();
549
-
550
- const result = parseProjectReadmeConfig(
551
- readme,
552
- 'https://github.com/orgs/example/projects/3',
553
- );
554
-
555
- expect(result.maximumPreparingIssuesCount).toBeUndefined();
556
- expect(consoleWarnSpy).toHaveBeenCalledWith(
557
- 'Unknown key "maximumPreparingIssueCount" in project README config section (project URL: https://github.com/orgs/example/projects/3)',
558
- );
559
-
560
- consoleWarnSpy.mockRestore();
561
- });
562
-
563
- it('should warn without project URL suffix when projectUrl is not provided', () => {
564
- const readme = `<details>
565
- <summary>config</summary>
566
- mysteryKey: 'value'
567
- </details>`;
568
- const consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation();
569
-
570
- parseProjectReadmeConfig(readme);
571
-
572
- expect(consoleWarnSpy).toHaveBeenCalledWith(
573
- 'Unknown key "mysteryKey" in project README config section',
574
- );
575
-
576
- consoleWarnSpy.mockRestore();
577
- });
578
- });
579
-
580
- describe('mergeConfigs', () => {
581
- it('should use configFile values when no overrides', () => {
582
- const configFile = {
583
- projectUrl: 'https://github.com/config/project',
584
- defaultAgentName: 'config-agent',
585
- };
586
-
587
- const result = mergeConfigs(configFile, {}, {});
588
-
589
- expect(result.projectUrl).toBe('https://github.com/config/project');
590
- expect(result.defaultAgentName).toBe('config-agent');
591
- });
592
-
593
- it('should use CLI overrides over configFile', () => {
594
- const configFile = {
595
- projectUrl: 'https://github.com/config/project',
596
- defaultAgentName: 'config-agent',
597
- };
598
- const cliOverrides = {
599
- defaultAgentName: 'cli-agent',
600
- };
601
-
602
- const result = mergeConfigs(configFile, cliOverrides, {});
603
-
604
- expect(result.projectUrl).toBe('https://github.com/config/project');
605
- expect(result.defaultAgentName).toBe('cli-agent');
606
- });
607
-
608
- it('should use README overrides over both CLI and configFile', () => {
609
- const configFile = {
610
- projectUrl: 'https://github.com/config/project',
611
- defaultAgentName: 'config-agent',
612
- };
613
- const cliOverrides = {
614
- defaultAgentName: 'cli-agent',
615
- };
616
- const readmeOverrides = {
617
- defaultAgentName: 'readme-agent',
618
- };
619
-
620
- const result = mergeConfigs(configFile, cliOverrides, readmeOverrides);
621
-
622
- expect(result.projectUrl).toBe('https://github.com/config/project');
623
- expect(result.defaultAgentName).toBe('readme-agent');
624
- });
625
-
626
- it('should preserve projectName from configFile', () => {
627
- const configFile = {
628
- projectName: 'my-project',
629
- projectUrl: 'https://github.com/config/project',
630
- };
631
-
632
- const result = mergeConfigs(configFile, {}, {});
633
-
634
- expect(result.projectName).toBe('my-project');
635
- });
636
- });
637
-
638
- describe('fetchProjectReadme', () => {
639
- it('should fetch README from GitHub GraphQL API', async () => {
640
- const responseBody = {
641
- data: {
642
- organization: {
643
- projectV2: { readme: '# Project README' },
644
- },
645
- },
646
- };
647
- jest
648
- .spyOn(global, 'fetch')
649
- .mockResolvedValue(
650
- new Response(JSON.stringify(responseBody), { status: 200 }),
651
- );
652
-
653
- const result = await fetchProjectReadme(
654
- 'https://github.com/orgs/test-org/projects/1',
655
- 'test-token',
656
- );
657
-
658
- expect(result).toBe('# Project README');
659
- });
660
-
661
- it('should try user if organization readme is null', async () => {
662
- const responseBody = {
663
- data: {
664
- organization: { projectV2: { readme: null } },
665
- user: { projectV2: { readme: '# User Project README' } },
666
- },
667
- };
668
- jest
669
- .spyOn(global, 'fetch')
670
- .mockResolvedValue(
671
- new Response(JSON.stringify(responseBody), { status: 200 }),
672
- );
673
-
674
- const result = await fetchProjectReadme(
675
- 'https://github.com/users/test-user/projects/1',
676
- 'test-token',
677
- );
678
-
679
- expect(result).toBe('# User Project README');
680
- });
681
-
682
- it('should fetch README for user-owned project when organization is null in response', async () => {
683
- const responseBody = {
684
- data: {
685
- organization: null,
686
- user: { projectV2: { readme: '# User Owned Project README' } },
687
- },
688
- errors: [
689
- {
690
- type: 'NOT_FOUND',
691
- message:
692
- "Could not resolve to an Organization with the login of 'test-user'.",
693
- },
694
- ],
695
- };
696
- jest
697
- .spyOn(global, 'fetch')
698
- .mockResolvedValue(
699
- new Response(JSON.stringify(responseBody), { status: 200 }),
700
- );
701
-
702
- const result = await fetchProjectReadme(
703
- 'https://github.com/users/test-user/projects/1',
704
- 'test-token',
705
- );
706
-
707
- expect(result).toBe('# User Owned Project README');
708
- });
709
-
710
- it('should return null and warn on failure', async () => {
711
- jest.spyOn(global, 'fetch').mockRejectedValue(new Error('Network error'));
712
-
713
- const consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation();
714
-
715
- const result = await fetchProjectReadme(
716
- 'https://github.com/orgs/test/projects/1',
717
- 'test-token',
718
- );
719
-
720
- expect(result).toBeNull();
721
- expect(consoleWarnSpy).toHaveBeenCalledWith(
722
- expect.stringContaining('Failed to fetch project README'),
723
- );
724
-
725
- consoleWarnSpy.mockRestore();
726
- });
727
-
728
- it('should reflect maximumPreparingIssuesCount from user-owned project README in merged config', async () => {
729
- const readmeWithConfig = [
730
- '# User Project',
731
- '<details>',
732
- '<summary>config</summary>',
733
- 'maximumPreparingIssuesCount: 7',
734
- '</details>',
735
- ].join('\n');
736
- const responseBody = {
737
- data: {
738
- organization: null,
739
- user: { projectV2: { readme: readmeWithConfig } },
740
- },
741
- errors: [
742
- {
743
- type: 'NOT_FOUND',
744
- message:
745
- "Could not resolve to an Organization with the login of 'test-user'.",
746
- },
747
- ],
748
- };
749
- jest
750
- .spyOn(global, 'fetch')
751
- .mockResolvedValue(
752
- new Response(JSON.stringify(responseBody), { status: 200 }),
753
- );
754
-
755
- const readme = await fetchProjectReadme(
756
- 'https://github.com/users/test-user/projects/1',
757
- 'test-token',
758
- );
759
- const readmeOverrides = parseProjectReadmeConfig(readme ?? '');
760
- const merged = mergeConfigs({}, {}, readmeOverrides);
761
-
762
- expect(merged.maximumPreparingIssuesCount).toBe(7);
763
- });
764
- });
765
-
766
- describe('startDaemon', () => {
767
- it('should read parameters from config file', async () => {
768
- const mockRun = jest.fn().mockResolvedValue({ rotationOrder: null });
769
- const MockedStartPreparationUseCase = jest.mocked(
770
- StartPreparationUseCase,
771
- );
772
-
773
- MockedStartPreparationUseCase.mockImplementation(function (
774
- this: StartPreparationUseCase,
775
- ) {
776
- this.run = mockRun;
777
- return this;
778
- });
779
-
780
- await program.parseAsync([
781
- 'node',
782
- 'test',
783
- 'startDaemon',
784
- '--configFilePath',
785
- configFilePath,
786
- ]);
787
-
788
- expect(mockRun).toHaveBeenCalledTimes(1);
789
- expect(mockRun).toHaveBeenCalledWith({
790
- projectUrl: 'https://github.com/orgs/test/projects/1',
791
- defaultAgentName: 'agent1',
792
- defaultLlmModelName: null,
793
- fallbackLlmModelName: null,
794
- defaultLlmAgentName: null,
795
- configFilePath: configFilePath,
796
- maximumPreparingIssuesCount: null,
797
- utilizationPercentageThreshold: 90,
798
- allowedIssueAuthors: null,
799
- manager: 'test-manager',
800
- codexHomeCandidates: null,
801
- labelsAsLlmAgentName: null,
802
- agents: null,
803
- normalConcurrentLimit: 6,
804
- maxConcurrentWorkers: 40,
805
- graphqlRateLimitFloor: 500,
806
- });
807
- });
808
-
809
- it('should pass the configured agent definitions to start preparation', async () => {
810
- const mockRun = jest.fn().mockResolvedValue({ rotationOrder: null });
811
- const MockedStartPreparationUseCase = jest.mocked(
812
- StartPreparationUseCase,
813
- );
814
-
815
- MockedStartPreparationUseCase.mockImplementation(function (
816
- this: StartPreparationUseCase,
817
- ) {
818
- this.run = mockRun;
819
- return this;
820
- });
821
- writeConfig({
822
- ...defaultConfig,
823
- agents: ['impl', 'chore', 'accounting'],
824
- });
825
-
826
- await program.parseAsync([
827
- 'node',
828
- 'test',
829
- 'startDaemon',
830
- '--configFilePath',
831
- configFilePath,
832
- ]);
833
-
834
- expect(mockRun).toHaveBeenCalledTimes(1);
835
- expect(mockRun).toHaveBeenCalledWith(
836
- expect.objectContaining({ agents: ['impl', 'chore', 'accounting'] }),
837
- );
838
- });
839
-
840
- it('should allow CLI args to override config file values', async () => {
841
- const mockRun = jest.fn().mockResolvedValue({ rotationOrder: null });
842
- const MockedStartPreparationUseCase = jest.mocked(
843
- StartPreparationUseCase,
844
- );
845
-
846
- MockedStartPreparationUseCase.mockImplementation(function (
847
- this: StartPreparationUseCase,
848
- ) {
849
- this.run = mockRun;
850
- return this;
851
- });
852
-
853
- await program.parseAsync([
854
- 'node',
855
- 'test',
856
- 'startDaemon',
857
- '--configFilePath',
858
- configFilePath,
859
- '--projectUrl',
860
- 'https://github.com/orgs/override/projects/2',
861
- '--defaultAgentName',
862
- 'override-agent',
863
- ]);
864
-
865
- expect(mockRun).toHaveBeenCalledTimes(1);
866
- expect(mockRun).toHaveBeenCalledWith({
867
- projectUrl: 'https://github.com/orgs/override/projects/2',
868
- defaultAgentName: 'override-agent',
869
- defaultLlmModelName: null,
870
- fallbackLlmModelName: null,
871
- defaultLlmAgentName: null,
872
- configFilePath: configFilePath,
873
- maximumPreparingIssuesCount: null,
874
- utilizationPercentageThreshold: 90,
875
- allowedIssueAuthors: null,
876
- manager: 'test-manager',
877
- codexHomeCandidates: null,
878
- labelsAsLlmAgentName: null,
879
- agents: null,
880
- normalConcurrentLimit: 6,
881
- maxConcurrentWorkers: 40,
882
- graphqlRateLimitFloor: 500,
883
- });
884
- });
885
-
886
- it('should pass defaultLlmModelName and allowedIssueAuthors from config file', async () => {
887
- const configWithLlm = {
888
- ...defaultConfig,
889
- defaultLlmModelName: 'claude-opus-4-5',
890
- allowedIssueAuthors: 'user1,user2',
891
- };
892
- writeConfig(configWithLlm);
893
-
894
- const mockRun = jest.fn().mockResolvedValue({ rotationOrder: null });
895
- const MockedStartPreparationUseCase = jest.mocked(
896
- StartPreparationUseCase,
897
- );
898
-
899
- MockedStartPreparationUseCase.mockImplementation(function (
900
- this: StartPreparationUseCase,
901
- ) {
902
- this.run = mockRun;
903
- return this;
904
- });
905
-
906
- await program.parseAsync([
907
- 'node',
908
- 'test',
909
- 'startDaemon',
910
- '--configFilePath',
911
- configFilePath,
912
- ]);
913
-
914
- expect(mockRun).toHaveBeenCalledWith(
915
- expect.objectContaining({
916
- defaultLlmModelName: 'claude-opus-4-5',
917
- allowedIssueAuthors: ['user1', 'user2'],
918
- }),
919
- );
920
- });
921
-
922
- it('should pass maximumPreparingIssuesCount from config file', async () => {
923
- const configWithCount = {
924
- ...defaultConfig,
925
- maximumPreparingIssuesCount: 10,
926
- };
927
- writeConfig(configWithCount);
928
-
929
- const mockRun = jest.fn().mockResolvedValue({ rotationOrder: null });
930
- const MockedStartPreparationUseCase = jest.mocked(
931
- StartPreparationUseCase,
932
- );
933
-
934
- MockedStartPreparationUseCase.mockImplementation(function (
935
- this: StartPreparationUseCase,
936
- ) {
937
- this.run = mockRun;
938
- return this;
939
- });
940
-
941
- await program.parseAsync([
942
- 'node',
943
- 'test',
944
- 'startDaemon',
945
- '--configFilePath',
946
- configFilePath,
947
- ]);
948
-
949
- expect(mockRun).toHaveBeenCalledWith(
950
- expect.objectContaining({
951
- maximumPreparingIssuesCount: 10,
952
- }),
953
- );
954
- });
955
-
956
- it('should pass maximumPreparingIssuesCount from CLI overriding config', async () => {
957
- const mockRun = jest.fn().mockResolvedValue({ rotationOrder: null });
958
- const MockedStartPreparationUseCase = jest.mocked(
959
- StartPreparationUseCase,
960
- );
961
-
962
- MockedStartPreparationUseCase.mockImplementation(function (
963
- this: StartPreparationUseCase,
964
- ) {
965
- this.run = mockRun;
966
- return this;
967
- });
968
-
969
- await program.parseAsync([
970
- 'node',
971
- 'test',
972
- 'startDaemon',
973
- '--configFilePath',
974
- configFilePath,
975
- '--maximumPreparingIssuesCount',
976
- '20',
977
- ]);
978
-
979
- expect(mockRun).toHaveBeenCalledWith(
980
- expect.objectContaining({
981
- maximumPreparingIssuesCount: 20,
982
- }),
983
- );
984
- });
985
-
986
- it('should exit with error for non-numeric maximumPreparingIssuesCount', async () => {
987
- const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation();
988
- const processExitSpy = jest
989
- .spyOn(process, 'exit')
990
- .mockImplementation(() => {
991
- throw new Error('process.exit called');
992
- });
993
-
994
- await expect(
995
- program.parseAsync([
996
- 'node',
997
- 'test',
998
- 'startDaemon',
999
- '--configFilePath',
1000
- configFilePath,
1001
- '--maximumPreparingIssuesCount',
1002
- 'abc',
1003
- ]),
1004
- ).rejects.toThrow('process.exit called');
1005
-
1006
- expect(consoleErrorSpy).toHaveBeenCalledWith(
1007
- 'Invalid value for --maximumPreparingIssuesCount. It must be a positive integer.',
1008
- );
1009
- expect(processExitSpy).toHaveBeenCalledWith(1);
1010
-
1011
- consoleErrorSpy.mockRestore();
1012
- processExitSpy.mockRestore();
1013
- });
1014
-
1015
- it('should exit with error for negative maximumPreparingIssuesCount', async () => {
1016
- const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation();
1017
- const processExitSpy = jest
1018
- .spyOn(process, 'exit')
1019
- .mockImplementation(() => {
1020
- throw new Error('process.exit called');
1021
- });
1022
-
1023
- await expect(
1024
- program.parseAsync([
1025
- 'node',
1026
- 'test',
1027
- 'startDaemon',
1028
- '--configFilePath',
1029
- configFilePath,
1030
- '--maximumPreparingIssuesCount',
1031
- '-5',
1032
- ]),
1033
- ).rejects.toThrow('process.exit called');
1034
-
1035
- expect(consoleErrorSpy).toHaveBeenCalledWith(
1036
- 'Invalid value for --maximumPreparingIssuesCount. It must be a positive integer.',
1037
- );
1038
- expect(processExitSpy).toHaveBeenCalledWith(1);
1039
-
1040
- consoleErrorSpy.mockRestore();
1041
- processExitSpy.mockRestore();
1042
- });
1043
-
1044
- it('should exit with error when GH_TOKEN is missing', async () => {
1045
- delete process.env.GH_TOKEN;
1046
- const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation();
1047
- const processExitSpy = jest
1048
- .spyOn(process, 'exit')
1049
- .mockImplementation(() => {
1050
- throw new Error('process.exit called');
1051
- });
1052
-
1053
- await expect(
1054
- program.parseAsync([
1055
- 'node',
1056
- 'test',
1057
- 'startDaemon',
1058
- '--configFilePath',
1059
- configFilePath,
1060
- ]),
1061
- ).rejects.toThrow('process.exit called');
1062
-
1063
- expect(consoleErrorSpy).toHaveBeenCalledWith(
1064
- 'GH_TOKEN environment variable is required',
1065
- );
1066
- expect(processExitSpy).toHaveBeenCalledWith(1);
1067
-
1068
- consoleErrorSpy.mockRestore();
1069
- processExitSpy.mockRestore();
1070
- });
1071
-
1072
- it('should exit with error when projectUrl is missing from both CLI and config', async () => {
1073
- const configWithoutProjectUrl = {
1074
- defaultAgentName: 'agent1',
1075
- };
1076
- writeConfig(configWithoutProjectUrl);
1077
-
1078
- const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation();
1079
- const processExitSpy = jest
1080
- .spyOn(process, 'exit')
1081
- .mockImplementation(() => {
1082
- throw new Error('process.exit called');
1083
- });
1084
-
1085
- await expect(
1086
- program.parseAsync([
1087
- 'node',
1088
- 'test',
1089
- 'startDaemon',
1090
- '--configFilePath',
1091
- configFilePath,
1092
- ]),
1093
- ).rejects.toThrow('process.exit called');
1094
-
1095
- expect(consoleErrorSpy).toHaveBeenCalledWith(
1096
- 'projectUrl is required. Provide via --projectUrl, config file, or project README.',
1097
- );
1098
- expect(processExitSpy).toHaveBeenCalledWith(1);
1099
-
1100
- consoleErrorSpy.mockRestore();
1101
- processExitSpy.mockRestore();
1102
- });
1103
-
1104
- it('should exit with error when defaultAgentName is missing', async () => {
1105
- const configMissing = {
1106
- projectUrl: 'https://github.com/orgs/test/projects/1',
1107
- };
1108
- writeConfig(configMissing);
1109
-
1110
- const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation();
1111
- const processExitSpy = jest
1112
- .spyOn(process, 'exit')
1113
- .mockImplementation(() => {
1114
- throw new Error('process.exit called');
1115
- });
1116
-
1117
- await expect(
1118
- program.parseAsync([
1119
- 'node',
1120
- 'test',
1121
- 'startDaemon',
1122
- '--configFilePath',
1123
- configFilePath,
1124
- ]),
1125
- ).rejects.toThrow('process.exit called');
1126
-
1127
- expect(consoleErrorSpy).toHaveBeenCalledWith(
1128
- 'defaultAgentName is required. Provide via --defaultAgentName, config file, or project README.',
1129
- );
1130
- expect(processExitSpy).toHaveBeenCalledWith(1);
1131
-
1132
- consoleErrorSpy.mockRestore();
1133
- processExitSpy.mockRestore();
1134
- });
1135
-
1136
- it('should exit with error when manager is missing', async () => {
1137
- const configMissing = {
1138
- projectUrl: 'https://github.com/orgs/test/projects/1',
1139
- defaultAgentName: 'agent1',
1140
- };
1141
- writeConfig(configMissing);
1142
-
1143
- const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation();
1144
- const processExitSpy = jest
1145
- .spyOn(process, 'exit')
1146
- .mockImplementation(() => {
1147
- throw new Error('process.exit called');
1148
- });
1149
-
1150
- await expect(
1151
- program.parseAsync([
1152
- 'node',
1153
- 'test',
1154
- 'startDaemon',
1155
- '--configFilePath',
1156
- configFilePath,
1157
- ]),
1158
- ).rejects.toThrow('process.exit called');
1159
-
1160
- expect(consoleErrorSpy).toHaveBeenCalledWith(
1161
- 'manager is required. Provide via the config file so that only issues assigned to the manager are picked up.',
1162
- );
1163
- expect(processExitSpy).toHaveBeenCalledWith(1);
1164
-
1165
- consoleErrorSpy.mockRestore();
1166
- processExitSpy.mockRestore();
1167
- });
1168
-
1169
- it('should log maximumPreparingIssuesCount before calling useCase.run', async () => {
1170
- const configWithValues = {
1171
- ...defaultConfig,
1172
- maximumPreparingIssuesCount: 10,
1173
- };
1174
- writeConfig(configWithValues);
1175
-
1176
- const callOrder: string[] = [];
1177
- const consoleLogSpy = jest
1178
- .spyOn(console, 'log')
1179
- .mockImplementation(() => {
1180
- callOrder.push('console.log');
1181
- });
1182
- const mockRun = jest.fn().mockImplementation(() => {
1183
- callOrder.push('useCase.run');
1184
- return Promise.resolve({ rotationOrder: null });
1185
- });
1186
- const MockedStartPreparationUseCase = jest.mocked(
1187
- StartPreparationUseCase,
1188
- );
1189
-
1190
- MockedStartPreparationUseCase.mockImplementation(function (
1191
- this: StartPreparationUseCase,
1192
- ) {
1193
- this.run = mockRun;
1194
- return this;
1195
- });
1196
-
1197
- await program.parseAsync([
1198
- 'node',
1199
- 'test',
1200
- 'startDaemon',
1201
- '--configFilePath',
1202
- configFilePath,
1203
- ]);
1204
-
1205
- expect(consoleLogSpy).toHaveBeenCalledWith(
1206
- 'maximumPreparingIssuesCount: 10',
1207
- );
1208
- expect(mockRun).toHaveBeenCalledTimes(1);
1209
- const logIndex = callOrder.indexOf('console.log');
1210
- const runIndex = callOrder.indexOf('useCase.run');
1211
- expect(logIndex).toBeLessThan(runIndex);
1212
-
1213
- consoleLogSpy.mockRestore();
1214
- });
1215
-
1216
- it('should apply README config overrides', async () => {
1217
- const readmeContent = [
1218
- '# Project',
1219
- '<details>',
1220
- '<summary>config</summary>',
1221
- 'defaultAgentName: readme-agent',
1222
- '</details>',
1223
- ].join('\n');
1224
- mockFetchReturningReadme(readmeContent);
1225
-
1226
- const mockRun = jest.fn().mockResolvedValue({ rotationOrder: null });
1227
- const MockedStartPreparationUseCase = jest.mocked(
1228
- StartPreparationUseCase,
1229
- );
1230
-
1231
- MockedStartPreparationUseCase.mockImplementation(function (
1232
- this: StartPreparationUseCase,
1233
- ) {
1234
- this.run = mockRun;
1235
- return this;
1236
- });
1237
-
1238
- await program.parseAsync([
1239
- 'node',
1240
- 'test',
1241
- 'startDaemon',
1242
- '--configFilePath',
1243
- configFilePath,
1244
- ]);
1245
-
1246
- expect(mockRun).toHaveBeenCalledWith(
1247
- expect.objectContaining({
1248
- defaultAgentName: 'readme-agent',
1249
- }),
1250
- );
1251
- });
1252
-
1253
- it('should pass codexHomeCandidates from config file', async () => {
1254
- const configWithCandidates = {
1255
- ...defaultConfig,
1256
- codexHomeCandidates: ['.codex-dev1', '.codex-dev2', '.codex-main'],
1257
- };
1258
- writeConfig(configWithCandidates);
1259
-
1260
- const mockRun = jest.fn().mockResolvedValue({ rotationOrder: null });
1261
- const MockedStartPreparationUseCase = jest.mocked(
1262
- StartPreparationUseCase,
1263
- );
1264
-
1265
- MockedStartPreparationUseCase.mockImplementation(function (
1266
- this: StartPreparationUseCase,
1267
- ) {
1268
- this.run = mockRun;
1269
- return this;
1270
- });
1271
-
1272
- await program.parseAsync([
1273
- 'node',
1274
- 'test',
1275
- 'startDaemon',
1276
- '--configFilePath',
1277
- configFilePath,
1278
- ]);
1279
-
1280
- expect(mockRun).toHaveBeenCalledWith(
1281
- expect.objectContaining({
1282
- codexHomeCandidates: ['.codex-dev1', '.codex-dev2', '.codex-main'],
1283
- }),
1284
- );
1285
- });
1286
-
1287
- it('should pass codexHomeCandidates as null when not in config', async () => {
1288
- const mockRun = jest.fn().mockResolvedValue({ rotationOrder: null });
1289
- const MockedStartPreparationUseCase = jest.mocked(
1290
- StartPreparationUseCase,
1291
- );
1292
-
1293
- MockedStartPreparationUseCase.mockImplementation(function (
1294
- this: StartPreparationUseCase,
1295
- ) {
1296
- this.run = mockRun;
1297
- return this;
1298
- });
1299
-
1300
- await program.parseAsync([
1301
- 'node',
1302
- 'test',
1303
- 'startDaemon',
1304
- '--configFilePath',
1305
- configFilePath,
1306
- ]);
1307
-
1308
- expect(mockRun).toHaveBeenCalledWith(
1309
- expect.objectContaining({
1310
- codexHomeCandidates: null,
1311
- }),
1312
- );
1313
- });
1314
-
1315
- it('should pass codexHomeCandidates from README config', async () => {
1316
- const readmeContent = [
1317
- '# Project',
1318
- '<details>',
1319
- '<summary>config</summary>',
1320
- 'codexHomeCandidates:',
1321
- ' - .codex-readme1',
1322
- ' - .codex-readme2',
1323
- '</details>',
1324
- ].join('\n');
1325
- mockFetchReturningReadme(readmeContent);
1326
-
1327
- const mockRun = jest.fn().mockResolvedValue({ rotationOrder: null });
1328
- const MockedStartPreparationUseCase = jest.mocked(
1329
- StartPreparationUseCase,
1330
- );
1331
-
1332
- MockedStartPreparationUseCase.mockImplementation(function (
1333
- this: StartPreparationUseCase,
1334
- ) {
1335
- this.run = mockRun;
1336
- return this;
1337
- });
1338
-
1339
- await program.parseAsync([
1340
- 'node',
1341
- 'test',
1342
- 'startDaemon',
1343
- '--configFilePath',
1344
- configFilePath,
1345
- ]);
1346
-
1347
- expect(mockRun).toHaveBeenCalledWith(
1348
- expect.objectContaining({
1349
- codexHomeCandidates: ['.codex-readme1', '.codex-readme2'],
1350
- }),
1351
- );
1352
- });
1353
-
1354
- it('should start web console before preparation cycle when consoleAccessToken is provided', async () => {
1355
- const mockRun = jest.fn().mockResolvedValue({ rotationOrder: null });
1356
- const MockedStartPreparationUseCase = jest.mocked(
1357
- StartPreparationUseCase,
1358
- );
1359
- MockedStartPreparationUseCase.mockImplementation(function (
1360
- this: StartPreparationUseCase,
1361
- ) {
1362
- this.run = mockRun;
1363
- return this;
1364
- });
1365
-
1366
- const mockKill = jest.fn();
1367
- const callOrder: string[] = [];
1368
- jest
1369
- .mocked(ensureConsoleRunningModule.ensureConsoleRunning)
1370
- .mockImplementationOnce(async () => {
1371
- callOrder.push('ensureWebConsoleRunning');
1372
- return { kill: mockKill };
1373
- });
1374
- mockRun.mockImplementationOnce(async () => {
1375
- callOrder.push('preparationRun');
1376
- return { rotationOrder: null };
1377
- });
1378
-
1379
- writeConfig({
1380
- ...defaultConfig,
1381
- consoleAccessToken: 'test-key-abc',
1382
- consoleProjects: {
1383
- acme: 'https://github.com/orgs/acme/projects/1',
1384
- globex: 'https://github.com/orgs/globex/projects/1',
1385
- },
1386
- });
1387
-
1388
- await program.parseAsync([
1389
- 'node',
1390
- 'test',
1391
- 'startDaemon',
1392
- '--configFilePath',
1393
- configFilePath,
1394
- ]);
1395
-
1396
- expect(callOrder).toEqual(['ensureWebConsoleRunning', 'preparationRun']);
1397
- expect(
1398
- ensureConsoleRunningModule.ensureConsoleRunning,
1399
- ).toHaveBeenCalledWith(configFilePath, 9980, ['acme', 'globex']);
1400
- });
1401
-
1402
- it('should not start web console when consoleAccessToken is not provided', async () => {
1403
- const mockRun = jest.fn().mockResolvedValue({ rotationOrder: null });
1404
- const MockedStartPreparationUseCase = jest.mocked(
1405
- StartPreparationUseCase,
1406
- );
1407
- MockedStartPreparationUseCase.mockImplementation(function (
1408
- this: StartPreparationUseCase,
1409
- ) {
1410
- this.run = mockRun;
1411
- return this;
1412
- });
1413
-
1414
- jest.mocked(ensureConsoleRunningModule.ensureConsoleRunning).mockClear();
1415
-
1416
- await program.parseAsync([
1417
- 'node',
1418
- 'test',
1419
- 'startDaemon',
1420
- '--configFilePath',
1421
- configFilePath,
1422
- ]);
1423
-
1424
- expect(
1425
- ensureConsoleRunningModule.ensureConsoleRunning,
1426
- ).not.toHaveBeenCalled();
1427
- });
1428
-
1429
- it('should register SIGTERM and SIGINT handlers when a new web console process is started', async () => {
1430
- const mockRun = jest.fn().mockResolvedValue({ rotationOrder: null });
1431
- const MockedStartPreparationUseCase = jest.mocked(
1432
- StartPreparationUseCase,
1433
- );
1434
- MockedStartPreparationUseCase.mockImplementation(function (
1435
- this: StartPreparationUseCase,
1436
- ) {
1437
- this.run = mockRun;
1438
- return this;
1439
- });
1440
-
1441
- const mockKill = jest.fn();
1442
- jest
1443
- .mocked(ensureConsoleRunningModule.ensureConsoleRunning)
1444
- .mockResolvedValueOnce({ kill: mockKill });
1445
-
1446
- const processOnceSpy = jest.spyOn(process, 'once');
1447
- const processExitSpy = jest
1448
- .spyOn(process, 'exit')
1449
- .mockImplementation(() => {
1450
- throw new Error('process.exit called');
1451
- });
1452
-
1453
- writeConfig({ ...defaultConfig, consoleAccessToken: 'test-key-sigterm' });
1454
-
1455
- await program.parseAsync([
1456
- 'node',
1457
- 'test',
1458
- 'startDaemon',
1459
- '--configFilePath',
1460
- configFilePath,
1461
- ]);
1462
-
1463
- const sigtermCall = processOnceSpy.mock.calls.find(
1464
- ([event]) => event === 'SIGTERM',
1465
- );
1466
- const sigintCall = processOnceSpy.mock.calls.find(
1467
- ([event]) => event === 'SIGINT',
1468
- );
1469
- expect(sigtermCall).toBeDefined();
1470
- expect(sigintCall).toBeDefined();
1471
-
1472
- processOnceSpy.mockRestore();
1473
-
1474
- const rawSigtermHandler = sigtermCall?.[1];
1475
- const rawSigintHandler = sigintCall?.[1];
1476
- if (typeof rawSigtermHandler !== 'function') {
1477
- throw new Error('Expected SIGTERM handler to be a function');
1478
- }
1479
- if (typeof rawSigintHandler !== 'function') {
1480
- throw new Error('Expected SIGINT handler to be a function');
1481
- }
1482
- expect(() => rawSigtermHandler()).toThrow('process.exit called');
1483
- expect(mockKill).toHaveBeenCalledTimes(1);
1484
- expect(processExitSpy).toHaveBeenCalledWith(0);
1485
- expect(() => rawSigintHandler()).toThrow('process.exit called');
1486
- expect(mockKill).toHaveBeenCalledTimes(2);
1487
- expect(processExitSpy).toHaveBeenCalledTimes(2);
1488
-
1489
- processExitSpy.mockRestore();
1490
- });
1491
- });
1492
-
1493
- describe('notifyFinishedIssuePreparation', () => {
1494
- it('should read parameters from config file', async () => {
1495
- const mockRun = jest.fn().mockResolvedValue({ rotationOrder: null });
1496
- const MockedNotifyFinishedUseCase = jest.mocked(
1497
- NotifyFinishedIssuePreparationUseCase,
1498
- );
1499
-
1500
- MockedNotifyFinishedUseCase.mockImplementation(function (
1501
- this: NotifyFinishedIssuePreparationUseCase,
1502
- ) {
1503
- this.run = mockRun;
1504
- return this;
1505
- });
1506
-
1507
- await program.parseAsync([
1508
- 'node',
1509
- 'test',
1510
- 'notifyFinishedIssuePreparation',
1511
- '--configFilePath',
1512
- configFilePath,
1513
- '--issueUrl',
1514
- 'https://github.com/test/repo/issues/1',
1515
- ]);
1516
-
1517
- expect(mockRun).toHaveBeenCalledTimes(1);
1518
- expect(mockRun).toHaveBeenCalledWith({
1519
- projectUrl: 'https://github.com/orgs/test/projects/1',
1520
- issueUrl: 'https://github.com/test/repo/issues/1',
1521
- thresholdForAutoReject: 3,
1522
- thresholdForDispatchLoop: 6,
1523
- workflowBlockerResolvedWebhookUrl: null,
1524
- allowedIssueAuthors: null,
1525
- labelsAsLlmAgentName: null,
1526
- labelsNotRequiringPullRequest: null,
1527
- changeTargetPathAliases: null,
1528
- agents: null,
1529
- missingAgentName: null,
1530
- sessionErrorLine: null,
1531
- manager: 'test-manager',
1532
- developerAgentNames: null,
1533
- deferPreparation: null,
1534
- });
1535
- });
1536
-
1537
- it('should allow CLI args to override config file values', async () => {
1538
- const mockRun = jest.fn().mockResolvedValue({ rotationOrder: null });
1539
- const MockedNotifyFinishedUseCase = jest.mocked(
1540
- NotifyFinishedIssuePreparationUseCase,
1541
- );
1542
-
1543
- MockedNotifyFinishedUseCase.mockImplementation(function (
1544
- this: NotifyFinishedIssuePreparationUseCase,
1545
- ) {
1546
- this.run = mockRun;
1547
- return this;
1548
- });
1549
-
1550
- await program.parseAsync([
1551
- 'node',
1552
- 'test',
1553
- 'notifyFinishedIssuePreparation',
1554
- '--configFilePath',
1555
- configFilePath,
1556
- '--issueUrl',
1557
- 'https://github.com/test/repo/issues/1',
1558
- '--projectUrl',
1559
- 'https://github.com/orgs/override/projects/2',
1560
- ]);
1561
-
1562
- expect(mockRun).toHaveBeenCalledWith({
1563
- projectUrl: 'https://github.com/orgs/override/projects/2',
1564
- issueUrl: 'https://github.com/test/repo/issues/1',
1565
- thresholdForAutoReject: 3,
1566
- thresholdForDispatchLoop: 6,
1567
- workflowBlockerResolvedWebhookUrl: null,
1568
- allowedIssueAuthors: null,
1569
- labelsAsLlmAgentName: null,
1570
- labelsNotRequiringPullRequest: null,
1571
- changeTargetPathAliases: null,
1572
- agents: null,
1573
- missingAgentName: null,
1574
- sessionErrorLine: null,
1575
- manager: 'test-manager',
1576
- developerAgentNames: null,
1577
- deferPreparation: null,
1578
- });
1579
- });
1580
-
1581
- it('should pass custom thresholdForAutoReject from config file', async () => {
1582
- const configWithThreshold = {
1583
- ...defaultConfig,
1584
- thresholdForAutoReject: 5,
1585
- };
1586
- writeConfig(configWithThreshold);
1587
-
1588
- const mockRun = jest.fn().mockResolvedValue({ rotationOrder: null });
1589
- const MockedNotifyFinishedUseCase = jest.mocked(
1590
- NotifyFinishedIssuePreparationUseCase,
1591
- );
1592
-
1593
- MockedNotifyFinishedUseCase.mockImplementation(function (
1594
- this: NotifyFinishedIssuePreparationUseCase,
1595
- ) {
1596
- this.run = mockRun;
1597
- return this;
1598
- });
1599
-
1600
- await program.parseAsync([
1601
- 'node',
1602
- 'test',
1603
- 'notifyFinishedIssuePreparation',
1604
- '--configFilePath',
1605
- configFilePath,
1606
- '--issueUrl',
1607
- 'https://github.com/test/repo/issues/1',
1608
- ]);
1609
-
1610
- expect(mockRun).toHaveBeenCalledWith(
1611
- expect.objectContaining({
1612
- thresholdForAutoReject: 5,
1613
- }),
1614
- );
1615
- });
1616
-
1617
- it('should pass custom thresholdForAutoReject from CLI overriding config', async () => {
1618
- const mockRun = jest.fn().mockResolvedValue({ rotationOrder: null });
1619
- const MockedNotifyFinishedUseCase = jest.mocked(
1620
- NotifyFinishedIssuePreparationUseCase,
1621
- );
1622
-
1623
- MockedNotifyFinishedUseCase.mockImplementation(function (
1624
- this: NotifyFinishedIssuePreparationUseCase,
1625
- ) {
1626
- this.run = mockRun;
1627
- return this;
1628
- });
1629
-
1630
- await program.parseAsync([
1631
- 'node',
1632
- 'test',
1633
- 'notifyFinishedIssuePreparation',
1634
- '--configFilePath',
1635
- configFilePath,
1636
- '--issueUrl',
1637
- 'https://github.com/test/repo/issues/1',
1638
- '--thresholdForAutoReject',
1639
- '7',
1640
- ]);
1641
-
1642
- expect(mockRun).toHaveBeenCalledWith(
1643
- expect.objectContaining({
1644
- thresholdForAutoReject: 7,
1645
- }),
1646
- );
1647
- });
1648
-
1649
- it('should exit with error for invalid thresholdForAutoReject', async () => {
1650
- const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation();
1651
- const processExitSpy = jest
1652
- .spyOn(process, 'exit')
1653
- .mockImplementation(() => {
1654
- throw new Error('process.exit called');
1655
- });
1656
-
1657
- await expect(
1658
- program.parseAsync([
1659
- 'node',
1660
- 'test',
1661
- 'notifyFinishedIssuePreparation',
1662
- '--configFilePath',
1663
- configFilePath,
1664
- '--issueUrl',
1665
- 'https://github.com/test/repo/issues/1',
1666
- '--thresholdForAutoReject',
1667
- 'abc',
1668
- ]),
1669
- ).rejects.toThrow('process.exit called');
1670
-
1671
- expect(consoleErrorSpy).toHaveBeenCalledWith(
1672
- 'Invalid value for --thresholdForAutoReject. It must be a positive integer.',
1673
- );
1674
- expect(processExitSpy).toHaveBeenCalledWith(1);
1675
-
1676
- consoleErrorSpy.mockRestore();
1677
- processExitSpy.mockRestore();
1678
- });
1679
-
1680
- it('should exit with error when GH_TOKEN is missing', async () => {
1681
- delete process.env.GH_TOKEN;
1682
- const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation();
1683
- const processExitSpy = jest
1684
- .spyOn(process, 'exit')
1685
- .mockImplementation(() => {
1686
- throw new Error('process.exit called');
1687
- });
1688
-
1689
- await expect(
1690
- program.parseAsync([
1691
- 'node',
1692
- 'test',
1693
- 'notifyFinishedIssuePreparation',
1694
- '--configFilePath',
1695
- configFilePath,
1696
- '--issueUrl',
1697
- 'https://github.com/test/repo/issues/1',
1698
- ]),
1699
- ).rejects.toThrow('process.exit called');
1700
-
1701
- expect(consoleErrorSpy).toHaveBeenCalledWith(
1702
- 'GH_TOKEN environment variable is required',
1703
- );
1704
- expect(processExitSpy).toHaveBeenCalledWith(1);
1705
-
1706
- consoleErrorSpy.mockRestore();
1707
- processExitSpy.mockRestore();
1708
- });
1709
-
1710
- it('should exit with error when projectUrl is missing', async () => {
1711
- const configMissing = {};
1712
- writeConfig(configMissing);
1713
-
1714
- const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation();
1715
- const processExitSpy = jest
1716
- .spyOn(process, 'exit')
1717
- .mockImplementation(() => {
1718
- throw new Error('process.exit called');
1719
- });
1720
-
1721
- await expect(
1722
- program.parseAsync([
1723
- 'node',
1724
- 'test',
1725
- 'notifyFinishedIssuePreparation',
1726
- '--configFilePath',
1727
- configFilePath,
1728
- '--issueUrl',
1729
- 'https://github.com/test/repo/issues/1',
1730
- ]),
1731
- ).rejects.toThrow('process.exit called');
1732
-
1733
- expect(consoleErrorSpy).toHaveBeenCalledWith(
1734
- 'projectUrl is required. Provide via --projectUrl, config file, or project README.',
1735
- );
1736
- expect(processExitSpy).toHaveBeenCalledWith(1);
1737
-
1738
- consoleErrorSpy.mockRestore();
1739
- processExitSpy.mockRestore();
1740
- });
1741
-
1742
- it('should pass workflowBlockerResolvedWebhookUrl from config file', async () => {
1743
- const configWithWebhook = {
1744
- ...defaultConfig,
1745
- workflowBlockerResolvedWebhookUrl:
1746
- 'https://example.com/webhook?url={URL}',
1747
- };
1748
- writeConfig(configWithWebhook);
1749
-
1750
- const mockRun = jest.fn().mockResolvedValue({ rotationOrder: null });
1751
- const MockedNotifyFinishedUseCase = jest.mocked(
1752
- NotifyFinishedIssuePreparationUseCase,
1753
- );
1754
-
1755
- MockedNotifyFinishedUseCase.mockImplementation(function (
1756
- this: NotifyFinishedIssuePreparationUseCase,
1757
- ) {
1758
- this.run = mockRun;
1759
- return this;
1760
- });
1761
-
1762
- await program.parseAsync([
1763
- 'node',
1764
- 'test',
1765
- 'notifyFinishedIssuePreparation',
1766
- '--configFilePath',
1767
- configFilePath,
1768
- '--issueUrl',
1769
- 'https://github.com/test/repo/issues/1',
1770
- ]);
1771
-
1772
- expect(mockRun).toHaveBeenCalledWith(
1773
- expect.objectContaining({
1774
- workflowBlockerResolvedWebhookUrl:
1775
- 'https://example.com/webhook?url={URL}',
1776
- }),
1777
- );
1778
- });
1779
-
1780
- it('should apply README config overrides for thresholdForAutoReject', async () => {
1781
- const readmeContent = [
1782
- '# Project',
1783
- '<details>',
1784
- '<summary>config</summary>',
1785
- 'thresholdForAutoReject: 9',
1786
- '</details>',
1787
- ].join('\n');
1788
- mockFetchReturningReadme(readmeContent);
1789
-
1790
- const mockRun = jest.fn().mockResolvedValue({ rotationOrder: null });
1791
- const MockedNotifyFinishedUseCase = jest.mocked(
1792
- NotifyFinishedIssuePreparationUseCase,
1793
- );
1794
-
1795
- MockedNotifyFinishedUseCase.mockImplementation(function (
1796
- this: NotifyFinishedIssuePreparationUseCase,
1797
- ) {
1798
- this.run = mockRun;
1799
- return this;
1800
- });
1801
-
1802
- await program.parseAsync([
1803
- 'node',
1804
- 'test',
1805
- 'notifyFinishedIssuePreparation',
1806
- '--configFilePath',
1807
- configFilePath,
1808
- '--issueUrl',
1809
- 'https://github.com/test/repo/issues/1',
1810
- ]);
1811
-
1812
- expect(mockRun).toHaveBeenCalledWith(
1813
- expect.objectContaining({
1814
- thresholdForAutoReject: 9,
1815
- }),
1816
- );
1817
- });
1818
-
1819
- it('passes manager from config file to useCase.run', async () => {
1820
- const configWithManager = {
1821
- ...defaultConfig,
1822
- manager: 'alice',
1823
- };
1824
- writeConfig(configWithManager);
1825
-
1826
- const mockRun = jest.fn().mockResolvedValue(undefined);
1827
- const MockedNotifyFinishedUseCase = jest.mocked(
1828
- NotifyFinishedIssuePreparationUseCase,
1829
- );
1830
-
1831
- MockedNotifyFinishedUseCase.mockImplementation(function (
1832
- this: NotifyFinishedIssuePreparationUseCase,
1833
- ) {
1834
- this.run = mockRun;
1835
- return this;
1836
- });
1837
-
1838
- await program.parseAsync([
1839
- 'node',
1840
- 'test',
1841
- 'notifyFinishedIssuePreparation',
1842
- '--configFilePath',
1843
- configFilePath,
1844
- '--issueUrl',
1845
- 'https://github.com/test/repo/issues/1',
1846
- ]);
1847
-
1848
- expect(mockRun).toHaveBeenCalledWith(
1849
- expect.objectContaining({ manager: 'alice' }),
1850
- );
1851
- });
1852
- });
1853
-
1854
- describe('checkIssueReviewReadiness', () => {
1855
- it('should appear in the CLI help output', () => {
1856
- const helpText = program.helpInformation();
1857
- expect(helpText).toContain('checkIssueReviewReadiness');
1858
- });
1859
-
1860
- it('should write reviewReady=true JSON to stdout when the use case reports zero rejections', async () => {
1861
- const mockRun = jest
1862
- .fn()
1863
- .mockResolvedValue({ reviewReady: true, rejections: [] });
1864
- const MockedCheckUseCase = jest.mocked(CheckIssueReviewReadinessUseCase);
1865
-
1866
- MockedCheckUseCase.mockImplementation(function (
1867
- this: CheckIssueReviewReadinessUseCase,
1868
- ) {
1869
- this.run = mockRun;
1870
- return this;
1871
- });
1872
-
1873
- const stdoutSpy = jest
1874
- .spyOn(process.stdout, 'write')
1875
- .mockImplementation(() => true);
1876
-
1877
- await program.parseAsync([
1878
- 'node',
1879
- 'test',
1880
- 'checkIssueReviewReadiness',
1881
- '--configFilePath',
1882
- configFilePath,
1883
- '--issueUrl',
1884
- 'https://github.com/test/repo/issues/1',
1885
- ]);
1886
-
1887
- expect(mockRun).toHaveBeenCalledTimes(1);
1888
- expect(mockRun).toHaveBeenCalledWith({
1889
- issueUrl: 'https://github.com/test/repo/issues/1',
1890
- allowedIssueAuthors: null,
1891
- labelsAsLlmAgentName: null,
1892
- labelsNotRequiringPullRequest: null,
1893
- developerAgentNames: null,
1894
- });
1895
- expect(stdoutSpy).toHaveBeenCalledWith(
1896
- `${JSON.stringify({ reviewReady: true, rejections: [] })}\n`,
1897
- );
1898
-
1899
- stdoutSpy.mockRestore();
1900
- });
1901
-
1902
- it('should write reviewReady=false JSON with rejections to stdout when the use case reports rejections', async () => {
1903
- const rejections = [
1904
- {
1905
- type: 'ANY_CI_JOB_FAILED_OR_IN_PROGRESS',
1906
- detail:
1907
- 'ANY_CI_JOB_FAILED_OR_IN_PROGRESS: https://github.com/test/repo/pull/1',
1908
- },
1909
- ];
1910
- const mockRun = jest
1911
- .fn()
1912
- .mockResolvedValue({ reviewReady: false, rejections });
1913
- const MockedCheckUseCase = jest.mocked(CheckIssueReviewReadinessUseCase);
1914
-
1915
- MockedCheckUseCase.mockImplementation(function (
1916
- this: CheckIssueReviewReadinessUseCase,
1917
- ) {
1918
- this.run = mockRun;
1919
- return this;
1920
- });
1921
-
1922
- const stdoutSpy = jest
1923
- .spyOn(process.stdout, 'write')
1924
- .mockImplementation(() => true);
1925
-
1926
- await program.parseAsync([
1927
- 'node',
1928
- 'test',
1929
- 'checkIssueReviewReadiness',
1930
- '--configFilePath',
1931
- configFilePath,
1932
- '--issueUrl',
1933
- 'https://github.com/test/repo/issues/1',
1934
- ]);
1935
-
1936
- expect(mockRun).toHaveBeenCalledTimes(1);
1937
- expect(stdoutSpy).toHaveBeenCalledWith(
1938
- `${JSON.stringify({ reviewReady: false, rejections })}\n`,
1939
- );
1940
-
1941
- stdoutSpy.mockRestore();
1942
- });
1943
-
1944
- it('should work without --projectUrl when config has no projectUrl', async () => {
1945
- const configMissing = {};
1946
- writeConfig(configMissing);
1947
-
1948
- const mockRun = jest
1949
- .fn()
1950
- .mockResolvedValue({ reviewReady: true, rejections: [] });
1951
- const MockedCheckUseCase = jest.mocked(CheckIssueReviewReadinessUseCase);
1952
-
1953
- MockedCheckUseCase.mockImplementation(function (
1954
- this: CheckIssueReviewReadinessUseCase,
1955
- ) {
1956
- this.run = mockRun;
1957
- return this;
1958
- });
1959
-
1960
- const stdoutSpy = jest
1961
- .spyOn(process.stdout, 'write')
1962
- .mockImplementation(() => true);
1963
-
1964
- await program.parseAsync([
1965
- 'node',
1966
- 'test',
1967
- 'checkIssueReviewReadiness',
1968
- '--configFilePath',
1969
- configFilePath,
1970
- '--issueUrl',
1971
- 'https://github.com/test/repo/issues/1',
1972
- ]);
1973
-
1974
- expect(mockRun).toHaveBeenCalledTimes(1);
1975
- expect(mockRun).toHaveBeenCalledWith({
1976
- issueUrl: 'https://github.com/test/repo/issues/1',
1977
- allowedIssueAuthors: null,
1978
- labelsAsLlmAgentName: null,
1979
- labelsNotRequiringPullRequest: null,
1980
- developerAgentNames: null,
1981
- });
1982
-
1983
- stdoutSpy.mockRestore();
1984
- });
1985
-
1986
- it('should exit with error when GH_TOKEN is missing', async () => {
1987
- delete process.env.GH_TOKEN;
1988
- const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation();
1989
- const processExitSpy = jest
1990
- .spyOn(process, 'exit')
1991
- .mockImplementation(() => {
1992
- throw new Error('process.exit called');
1993
- });
1994
-
1995
- await expect(
1996
- program.parseAsync([
1997
- 'node',
1998
- 'test',
1999
- 'checkIssueReviewReadiness',
2000
- '--configFilePath',
2001
- configFilePath,
2002
- '--issueUrl',
2003
- 'https://github.com/test/repo/issues/1',
2004
- ]),
2005
- ).rejects.toThrow('process.exit called');
2006
-
2007
- expect(consoleErrorSpy).toHaveBeenCalledWith(
2008
- 'GH_TOKEN environment variable is required',
2009
- );
2010
- expect(processExitSpy).toHaveBeenCalledWith(1);
2011
-
2012
- consoleErrorSpy.mockRestore();
2013
- processExitSpy.mockRestore();
2014
- });
2015
- });
2016
-
2017
- describe('serveWeb', () => {
2018
- it('should appear in the CLI help output', () => {
2019
- const helpText = program.helpInformation();
2020
- expect(helpText).toContain('serveWeb');
2021
- });
2022
-
2023
- it('should start the server on the default port 9980 when --port is omitted', async () => {
2024
- writeConfig({ ...defaultConfig, consoleAccessToken: 'config-token' });
2025
- const logSpy = jest.spyOn(console, 'log').mockImplementation();
2026
-
2027
- await program.parseAsync([
2028
- 'node',
2029
- 'test',
2030
- 'serveWeb',
2031
- '--configFilePath',
2032
- configFilePath,
2033
- '--dashboardProjectNames',
2034
- 'acme,globex',
2035
- ]);
2036
-
2037
- expect(mockStartWebServer).toHaveBeenCalledTimes(1);
2038
- const callArg = mockStartWebServer.mock.calls[0][0];
2039
- expect(callArg.port).toBe(9980);
2040
- expect(callArg.accessToken).toBe('config-token');
2041
- expect(callArg.consoleDataOutputDir).toBeNull();
2042
- expect(callArg.dashboardProjectNames).toEqual(['acme', 'globex']);
2043
- expect(callArg.uiDistDir).toBe(
2044
- path.join(__dirname, '..', 'console', 'ui-dist'),
2045
- );
2046
- expect(fs.existsSync(path.join(callArg.uiDistDir, 'index.html'))).toBe(
2047
- true,
2048
- );
2049
-
2050
- logSpy.mockRestore();
2051
- });
2052
-
2053
- it('should exit with error when --dashboardProjectNames is omitted', async () => {
2054
- writeConfig({ ...defaultConfig, consoleAccessToken: 'config-token' });
2055
- const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation();
2056
- const processExitSpy = jest
2057
- .spyOn(process, 'exit')
2058
- .mockImplementation(() => {
2059
- throw new Error('process.exit called');
2060
- });
2061
-
2062
- await expect(
2063
- program.parseAsync([
2064
- 'node',
2065
- 'test',
2066
- 'serveWeb',
2067
- '--configFilePath',
2068
- configFilePath,
2069
- ]),
2070
- ).rejects.toThrow('process.exit called');
2071
-
2072
- expect(consoleErrorSpy).toHaveBeenCalledWith(
2073
- '--dashboardProjectNames must list at least one project name',
2074
- );
2075
- expect(processExitSpy).toHaveBeenCalledWith(1);
2076
- expect(mockStartWebServer).not.toHaveBeenCalled();
2077
-
2078
- consoleErrorSpy.mockRestore();
2079
- processExitSpy.mockRestore();
2080
- });
2081
-
2082
- it('should exit with error when two --dashboardProjectNames share a display label', async () => {
2083
- writeConfig({ ...defaultConfig, consoleAccessToken: 'config-token' });
2084
- const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation();
2085
- const processExitSpy = jest
2086
- .spyOn(process, 'exit')
2087
- .mockImplementation(() => {
2088
- throw new Error('process.exit called');
2089
- });
2090
-
2091
- await expect(
2092
- program.parseAsync([
2093
- 'node',
2094
- 'test',
2095
- 'serveWeb',
2096
- '--configFilePath',
2097
- configFilePath,
2098
- '--dashboardProjectNames',
2099
- 'acme,acmelabs',
2100
- ]),
2101
- ).rejects.toThrow('process.exit called');
2102
-
2103
- expect(consoleErrorSpy).toHaveBeenCalledWith(
2104
- 'Dashboard project names acme and acmelabs share the display label ac',
2105
- );
2106
- expect(processExitSpy).toHaveBeenCalledWith(1);
2107
- expect(mockStartWebServer).not.toHaveBeenCalled();
2108
-
2109
- consoleErrorSpy.mockRestore();
2110
- processExitSpy.mockRestore();
2111
- });
2112
-
2113
- it('should validate --dashboardProjectNames before constructing the GitHub repositories', async () => {
2114
- writeConfig({ ...defaultConfig, consoleAccessToken: 'config-token' });
2115
- jest.mocked(GraphqlProjectRepository).mockClear();
2116
- const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation();
2117
- const processExitSpy = jest
2118
- .spyOn(process, 'exit')
2119
- .mockImplementation(() => {
2120
- throw new Error('process.exit called');
2121
- });
2122
-
2123
- await expect(
2124
- program.parseAsync([
2125
- 'node',
2126
- 'test',
2127
- 'serveWeb',
2128
- '--configFilePath',
2129
- configFilePath,
2130
- ]),
2131
- ).rejects.toThrow('process.exit called');
2132
-
2133
- expect(jest.mocked(GraphqlProjectRepository)).not.toHaveBeenCalled();
2134
-
2135
- consoleErrorSpy.mockRestore();
2136
- processExitSpy.mockRestore();
2137
- });
2138
-
2139
- it('should use the provided --port and --consoleDataOutputDir', async () => {
2140
- writeConfig({ ...defaultConfig, consoleAccessToken: 'config-token' });
2141
- const logSpy = jest.spyOn(console, 'log').mockImplementation();
2142
-
2143
- await program.parseAsync([
2144
- 'node',
2145
- 'test',
2146
- 'serveWeb',
2147
- '--configFilePath',
2148
- configFilePath,
2149
- '--port',
2150
- '12345',
2151
- '--consoleDataOutputDir',
2152
- '/tmp/console-data',
2153
- '--dashboardProjectNames',
2154
- 'acme,globex',
2155
- ]);
2156
-
2157
- const callArg = mockStartWebServer.mock.calls[0][0];
2158
- expect(callArg.port).toBe(12345);
2159
- expect(callArg.consoleDataOutputDir).toBe('/tmp/console-data');
2160
-
2161
- logSpy.mockRestore();
2162
- });
2163
-
2164
- it('should exit with error when consoleAccessToken is missing from config', async () => {
2165
- writeConfig(defaultConfig);
2166
- const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation();
2167
- const processExitSpy = jest
2168
- .spyOn(process, 'exit')
2169
- .mockImplementation(() => {
2170
- throw new Error('process.exit called');
2171
- });
2172
-
2173
- await expect(
2174
- program.parseAsync([
2175
- 'node',
2176
- 'test',
2177
- 'serveWeb',
2178
- '--configFilePath',
2179
- configFilePath,
2180
- ]),
2181
- ).rejects.toThrow('process.exit called');
2182
-
2183
- expect(consoleErrorSpy).toHaveBeenCalledWith(
2184
- 'consoleAccessToken is required. Provide it via the config file.',
2185
- );
2186
- expect(processExitSpy).toHaveBeenCalledWith(1);
2187
-
2188
- consoleErrorSpy.mockRestore();
2189
- processExitSpy.mockRestore();
2190
- });
2191
-
2192
- it('should exit with error for an invalid --port value', async () => {
2193
- writeConfig({ ...defaultConfig, consoleAccessToken: 'config-token' });
2194
- const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation();
2195
- const processExitSpy = jest
2196
- .spyOn(process, 'exit')
2197
- .mockImplementation(() => {
2198
- throw new Error('process.exit called');
2199
- });
2200
-
2201
- await expect(
2202
- program.parseAsync([
2203
- 'node',
2204
- 'test',
2205
- 'serveWeb',
2206
- '--configFilePath',
2207
- configFilePath,
2208
- '--port',
2209
- 'not-a-number',
2210
- ]),
2211
- ).rejects.toThrow('process.exit called');
2212
-
2213
- expect(processExitSpy).toHaveBeenCalledWith(1);
2214
-
2215
- consoleErrorSpy.mockRestore();
2216
- processExitSpy.mockRestore();
2217
- });
2218
-
2219
- it('should inject the resolved project and issue repository into the server', async () => {
2220
- writeConfig({ ...defaultConfig, consoleAccessToken: 'config-token' });
2221
- const logSpy = jest.spyOn(console, 'log').mockImplementation();
2222
-
2223
- await program.parseAsync([
2224
- 'node',
2225
- 'test',
2226
- 'serveWeb',
2227
- '--configFilePath',
2228
- configFilePath,
2229
- '--dashboardProjectNames',
2230
- 'acme,globex',
2231
- ]);
2232
-
2233
- const callArg = mockStartWebServer.mock.calls[0][0];
2234
- expect(callArg.issueRepository).not.toBeNull();
2235
- expect(callArg.issueTitleStateCache).not.toBeNull();
2236
-
2237
- const resolveProject = callArg.resolveProject;
2238
- if (resolveProject === null || resolveProject === undefined) {
2239
- throw new Error('resolveProject was not injected');
2240
- }
2241
- const binding = await resolveProject('test-project');
2242
- expect(binding).not.toBeNull();
2243
- expect(binding?.pjcode).toBe('test-project');
2244
- expect(binding?.project.name).toBe('test-project');
2245
-
2246
- const unknownBinding = await resolveProject('not-configured');
2247
- expect(unknownBinding).toBeNull();
2248
-
2249
- logSpy.mockRestore();
2250
- });
2251
-
2252
- it('should exit with error when GH_TOKEN is missing', async () => {
2253
- delete process.env.GH_TOKEN;
2254
- writeConfig({ ...defaultConfig, consoleAccessToken: 'config-token' });
2255
- const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation();
2256
- const processExitSpy = jest
2257
- .spyOn(process, 'exit')
2258
- .mockImplementation(() => {
2259
- throw new Error('process.exit called');
2260
- });
2261
-
2262
- await expect(
2263
- program.parseAsync([
2264
- 'node',
2265
- 'test',
2266
- 'serveWeb',
2267
- '--configFilePath',
2268
- configFilePath,
2269
- ]),
2270
- ).rejects.toThrow('process.exit called');
2271
-
2272
- expect(consoleErrorSpy).toHaveBeenCalledWith(
2273
- 'GH_TOKEN environment variable is required',
2274
- );
2275
-
2276
- consoleErrorSpy.mockRestore();
2277
- processExitSpy.mockRestore();
2278
- });
2279
-
2280
- it('should exit with error when projectUrl is missing from config', async () => {
2281
- writeConfig({
2282
- defaultAgentName: 'agent1',
2283
- projectName: 'test-project',
2284
- consoleAccessToken: 'config-token',
2285
- });
2286
- mockFetchReturningReadme(null);
2287
- const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation();
2288
- const processExitSpy = jest
2289
- .spyOn(process, 'exit')
2290
- .mockImplementation(() => {
2291
- throw new Error('process.exit called');
2292
- });
2293
-
2294
- await expect(
2295
- program.parseAsync([
2296
- 'node',
2297
- 'test',
2298
- 'serveWeb',
2299
- '--configFilePath',
2300
- configFilePath,
2301
- ]),
2302
- ).rejects.toThrow('process.exit called');
2303
-
2304
- expect(consoleErrorSpy).toHaveBeenCalledWith(
2305
- 'projectUrl is required. Provide it via the config file or project README.',
2306
- );
2307
-
2308
- consoleErrorSpy.mockRestore();
2309
- processExitSpy.mockRestore();
2310
- });
2311
- });
2312
-
2313
- describe('serveConsole (deprecated alias)', () => {
2314
- it('should appear in the CLI help output', () => {
2315
- const helpText = program.helpInformation();
2316
- expect(helpText).toContain('serveConsole');
2317
- });
2318
-
2319
- it('should route to the same handler as serveWeb on the default port 9980', async () => {
2320
- writeConfig({ ...defaultConfig, consoleAccessToken: 'config-token' });
2321
- const logSpy = jest.spyOn(console, 'log').mockImplementation();
2322
-
2323
- await program.parseAsync([
2324
- 'node',
2325
- 'test',
2326
- 'serveConsole',
2327
- '--configFilePath',
2328
- configFilePath,
2329
- '--dashboardProjectNames',
2330
- 'acme,globex',
2331
- ]);
2332
-
2333
- expect(mockStartWebServer).toHaveBeenCalledTimes(1);
2334
- const callArg = mockStartWebServer.mock.calls[0][0];
2335
- expect(callArg.port).toBe(9980);
2336
- expect(callArg.accessToken).toBe('config-token');
2337
- expect(callArg.consoleDataOutputDir).toBeNull();
2338
- expect(callArg.uiDistDir).toBe(
2339
- path.join(__dirname, '..', 'console', 'ui-dist'),
2340
- );
2341
-
2342
- logSpy.mockRestore();
2343
- });
2344
-
2345
- it('should use the provided --port and --consoleDataOutputDir', async () => {
2346
- writeConfig({ ...defaultConfig, consoleAccessToken: 'config-token' });
2347
- const logSpy = jest.spyOn(console, 'log').mockImplementation();
2348
-
2349
- await program.parseAsync([
2350
- 'node',
2351
- 'test',
2352
- 'serveConsole',
2353
- '--configFilePath',
2354
- configFilePath,
2355
- '--port',
2356
- '12345',
2357
- '--consoleDataOutputDir',
2358
- '/tmp/console-data',
2359
- '--dashboardProjectNames',
2360
- 'acme,globex',
2361
- ]);
2362
-
2363
- const callArg = mockStartWebServer.mock.calls[0][0];
2364
- expect(callArg.port).toBe(12345);
2365
- expect(callArg.consoleDataOutputDir).toBe('/tmp/console-data');
2366
-
2367
- logSpy.mockRestore();
2368
- });
2369
- });
2370
-
2371
- describe('killTmuxSession', () => {
2372
- it('reaches the real repository command sequence to kill a named session via --session', async () => {
2373
- await program.parseAsync([
2374
- 'node',
2375
- 'test',
2376
- 'killTmuxSession',
2377
- '--session',
2378
- 'https_//github_com/owner/repo/issues/9',
2379
- ]);
2380
-
2381
- const expectedScopeUnitName =
2382
- 'cl-https---github-com-owner-repo-issues-9.scope';
2383
- expect(mockRunCommand.mock.calls).toEqual([
2384
- ['systemctl', ['--user', 'reset-failed', expectedScopeUnitName]],
2385
- ['systemctl', ['--user', 'stop', expectedScopeUnitName]],
2386
- ['systemctl', ['--user', 'reset-failed', expectedScopeUnitName]],
2387
- [
2388
- 'tmux',
2389
- ['kill-session', '-t', '=https_//github_com/owner/repo/issues/9'],
2390
- ],
2391
- ]);
2392
- });
2393
-
2394
- it('reaches the real repository command sequence to stop its own scope via --self, without calling tmux', async () => {
2395
- const actualFs = jest.requireActual<typeof fs>('fs');
2396
- const readFileSyncMock = jest.mocked(fs.readFileSync);
2397
- readFileSyncMock.mockImplementation(
2398
- (
2399
- filePath: Parameters<typeof fs.readFileSync>[0],
2400
- options: Parameters<typeof fs.readFileSync>[1],
2401
- ): ReturnType<typeof fs.readFileSync> => {
2402
- if (filePath === '/proc/self/cgroup') {
2403
- return '0::/user.slice/user-1000.slice/user@1000.service/app.slice/cl-current-session.scope\n';
2404
- }
2405
- return actualFs.readFileSync(filePath, options);
2406
- },
2407
- );
2408
-
2409
- await program.parseAsync(['node', 'test', 'killTmuxSession', '--self']);
2410
-
2411
- expect(mockRunCommand.mock.calls).toEqual([
2412
- ['systemctl', ['--user', 'reset-failed', 'cl-current-session.scope']],
2413
- ['systemctl', ['--user', 'stop', 'cl-current-session.scope']],
2414
- ['systemctl', ['--user', 'reset-failed', 'cl-current-session.scope']],
2415
- ]);
2416
- expect(mockRunCommand.mock.calls.some((call) => call[0] === 'tmux')).toBe(
2417
- false,
2418
- );
2419
-
2420
- readFileSyncMock.mockImplementation(actualFs.readFileSync);
2421
- });
2422
-
2423
- it('exits with an error when neither --session nor --self is provided', async () => {
2424
- const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation();
2425
- const processExitSpy = jest
2426
- .spyOn(process, 'exit')
2427
- .mockImplementation(() => {
2428
- throw new Error('process.exit called');
2429
- });
2430
-
2431
- await expect(
2432
- program.parseAsync(['node', 'test', 'killTmuxSession']),
2433
- ).rejects.toThrow('process.exit called');
2434
-
2435
- expect(consoleErrorSpy).toHaveBeenCalledWith(
2436
- 'Either --session <name> or --self is required',
2437
- );
2438
- expect(processExitSpy).toHaveBeenCalledWith(1);
2439
-
2440
- consoleErrorSpy.mockRestore();
2441
- processExitSpy.mockRestore();
2442
- });
2443
-
2444
- it('exits with an error when both --session and --self are provided', async () => {
2445
- const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation();
2446
- const processExitSpy = jest
2447
- .spyOn(process, 'exit')
2448
- .mockImplementation(() => {
2449
- throw new Error('process.exit called');
2450
- });
2451
-
2452
- await expect(
2453
- program.parseAsync([
2454
- 'node',
2455
- 'test',
2456
- 'killTmuxSession',
2457
- '--session',
2458
- 'some_session',
2459
- '--self',
2460
- ]),
2461
- ).rejects.toThrow('process.exit called');
2462
-
2463
- expect(consoleErrorSpy).toHaveBeenCalledWith(
2464
- '--session and --self cannot be used together',
2465
- );
2466
- expect(processExitSpy).toHaveBeenCalledWith(1);
2467
-
2468
- consoleErrorSpy.mockRestore();
2469
- processExitSpy.mockRestore();
2470
- });
2471
- });
2472
-
2473
- describe('attachOrCreate', () => {
2474
- const issueUrl = 'https://github.com/owner/repo/issues/9';
2475
- let scopeLibPath = '';
2476
-
2477
- beforeEach(() => {
2478
- scopeLibPath = path.join(
2479
- os.tmpdir(),
2480
- `test-cl-scope-lib-${Date.now()}.sh`,
2481
- );
2482
- fs.writeFileSync(scopeLibPath, '#!/bin/bash\n');
2483
- process.env.CL_SCOPE_LIB_PATH = scopeLibPath;
2484
- });
2485
-
2486
- afterEach(() => {
2487
- if (fs.existsSync(scopeLibPath)) {
2488
- fs.unlinkSync(scopeLibPath);
2489
- }
2490
- delete process.env.CL_SCOPE_LIB_PATH;
2491
- });
2492
-
2493
- it('attaches to the registered session when registry returns a live session', async () => {
2494
- mockRunCommand
2495
- .mockResolvedValueOnce({ stdout: 'wq7x3mk\n', stderr: '', exitCode: 0 })
2496
- .mockResolvedValueOnce({ stdout: '', stderr: '', exitCode: 0 });
2497
-
2498
- await program.parseAsync([
2499
- 'node',
2500
- 'test',
2501
- 'attachOrCreate',
2502
- '--issueUrl',
2503
- issueUrl,
2504
- ]);
2505
-
2506
- expect(mockRunCommand.mock.calls[0]).toEqual([
2507
- 'bash',
2508
- [scopeLibPath, 'registry-get-session', issueUrl],
2509
- ]);
2510
- expect(mockRunCommand.mock.calls[1]).toEqual([
2511
- 'tmux',
2512
- ['has-session', '-t', '=wq7x3mk'],
2513
- ]);
2514
- expect(mockSpawnInteractive).toHaveBeenCalledWith('tmux', [
2515
- 'attach-session',
2516
- '-t',
2517
- '=wq7x3mk',
2518
- ]);
2519
- });
2520
-
2521
- it('creates a new session when no registered session is found', async () => {
2522
- mockRunCommand.mockResolvedValueOnce({
2523
- stdout: '',
2524
- stderr: '',
2525
- exitCode: 0,
2526
- });
2527
-
2528
- await program.parseAsync([
2529
- 'node',
2530
- 'test',
2531
- 'attachOrCreate',
2532
- '--issueUrl',
2533
- issueUrl,
2534
- ]);
2535
-
2536
- expect(mockSpawnInteractive).toHaveBeenCalledWith('tmux', [
2537
- 'new-session',
2538
- '-A',
2539
- '-s',
2540
- issueUrl,
2541
- 'cl',
2542
- issueUrl,
2543
- ]);
2544
- });
2545
-
2546
- it('creates a new session when no scope lib path is available', async () => {
2547
- jest
2548
- .mocked(fs.existsSync)
2549
- .mockReturnValueOnce(false)
2550
- .mockReturnValueOnce(false);
2551
-
2552
- await program.parseAsync([
2553
- 'node',
2554
- 'test',
2555
- 'attachOrCreate',
2556
- '--issueUrl',
2557
- issueUrl,
2558
- ]);
2559
-
2560
- expect(mockRunCommand.mock.calls).toHaveLength(0);
2561
- expect(mockSpawnInteractive).toHaveBeenCalledWith('tmux', [
2562
- 'new-session',
2563
- '-A',
2564
- '-s',
2565
- issueUrl,
2566
- 'cl',
2567
- issueUrl,
2568
- ]);
2569
- });
2570
- });
2571
-
2572
- describe('ownerCallFileAppend and ownerCallFileDelete', () => {
2573
- const issueUrl = 'https://github.com/OWNER/REPO/issues/1';
2574
- const otherIssueUrl = 'https://github.com/OWNER/REPO/issues/2';
2575
- const sessionName = toTmuxSessionName(issueUrl);
2576
- let ownerCallDataDir = '';
2577
- let bodyFilePath = '';
2578
-
2579
- beforeEach(() => {
2580
- ownerCallDataDir = fs.mkdtempSync(
2581
- path.join(os.tmpdir(), 'owner-call-cli-'),
2582
- );
2583
- bodyFilePath = path.join(ownerCallDataDir, 'body.txt');
2584
- fs.writeFileSync(bodyFilePath, ' the call body\n\nsecond paragraph\n');
2585
- });
2586
-
2587
- afterEach(() => {
2588
- fs.rmSync(ownerCallDataDir, { recursive: true, force: true });
2589
- });
2590
-
2591
- // The in-tmux-by-human data the scheduled run writes into the same
2592
- // directory serveWeb serves: one file per project, each listing the
2593
- // sessions of that project by their raw issue url.
2594
- type InTmuxByHumanProjectData = {
2595
- projectCode: string;
2596
- sessionIssueUrls: string[];
2597
- };
2598
-
2599
- const writeInTmuxByHumanData = (
2600
- projects: InTmuxByHumanProjectData[],
2601
- ): void => {
2602
- for (const project of projects) {
2603
- fs.writeFileSync(
2604
- path.join(ownerCallDataDir, `${project.projectCode}.v4.json`),
2605
- `${JSON.stringify({
2606
- version: 4,
2607
- overviewUrl: 'https://github.com/orgs/OWNER/projects/1',
2608
- tdpmConsoleUrl: 'http://localhost/projects/code',
2609
- newIssueUrl: 'https://github.com/OWNER/REPO/issues/new',
2610
- groups: [
2611
- {
2612
- story: 'a story',
2613
- sessions: project.sessionIssueUrls.map((url) => ({
2614
- name: url,
2615
- description: 'an issue title',
2616
- })),
2617
- },
2618
- ],
2619
- })}\n`,
2620
- );
2621
- }
2622
- };
2623
-
2624
- const projectCodeListing = (
2625
- projects: InTmuxByHumanProjectData[],
2626
- url: string,
2627
- ): string | null =>
2628
- projects.find((project) => project.sessionIssueUrls.includes(url))
2629
- ?.projectCode ?? null;
2630
-
2631
- const appendedFilePath = (projectCode: string | null): string =>
2632
- path.join(
2633
- ownerCallDataDir,
2634
- ownerCallFileRelativePath(projectCode, sessionName),
2635
- );
2636
-
2637
- const runAppend = (calledAt: string): Promise<unknown> =>
2638
- program.parseAsync([
2639
- 'node',
2640
- 'test',
2641
- 'ownerCallFileAppend',
2642
- '--session',
2643
- sessionName,
2644
- '--calledAt',
2645
- calledAt,
2646
- '--body-file',
2647
- bodyFilePath,
2648
- '--inTmuxDataDir',
2649
- ownerCallDataDir,
2650
- ]);
2651
-
2652
- it('appends the call under the directory of the project whose session list holds the session', async () => {
2653
- const projects: InTmuxByHumanProjectData[] = [
2654
- { projectCode: 'other', sessionIssueUrls: [otherIssueUrl] },
2655
- { projectCode: 'umino', sessionIssueUrls: [issueUrl] },
2656
- ];
2657
- writeInTmuxByHumanData(projects);
2658
- const stdoutSpy = jest
2659
- .spyOn(process.stdout, 'write')
2660
- .mockImplementation(() => true);
2661
-
2662
- await runAppend('2026-08-14T04:22:28Z');
2663
-
2664
- const documents = YAML.parseAllDocuments(
2665
- fs.readFileSync(
2666
- appendedFilePath(projectCodeListing(projects, issueUrl)),
2667
- 'utf-8',
2668
- ),
2669
- );
2670
- expect(documents).toHaveLength(1);
2671
- expect(documents[0].toJS()).toEqual({
2672
- sessionName,
2673
- calledAt: '2026-08-14T04:22:28Z',
2674
- body: ' the call body\n\nsecond paragraph\n',
2675
- });
2676
- expect(stdoutSpy).not.toHaveBeenCalled();
2677
-
2678
- stdoutSpy.mockRestore();
2679
- });
2680
-
2681
- it('appends the call under NA when no project lists the session', async () => {
2682
- const projects: InTmuxByHumanProjectData[] = [
2683
- { projectCode: 'umino', sessionIssueUrls: [otherIssueUrl] },
2684
- ];
2685
- writeInTmuxByHumanData(projects);
2686
-
2687
- await runAppend('2026-08-14T04:22:28Z');
2688
-
2689
- expect(projectCodeListing(projects, issueUrl)).toBeNull();
2690
- expect(fs.existsSync(appendedFilePath(null))).toBe(true);
2691
- });
2692
-
2693
- it('rejects a calledAt that is not a UTC ISO-8601 timestamp with second precision', async () => {
2694
- const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation();
2695
- const processExitSpy = jest
2696
- .spyOn(process, 'exit')
2697
- .mockImplementation(() => {
2698
- throw new Error('process.exit called');
2699
- });
2700
-
2701
- await expect(runAppend('2026-08-14 04:22:28')).rejects.toThrow(
2702
- 'process.exit called',
2703
- );
2704
- expect(processExitSpy).toHaveBeenCalledWith(1);
2705
- expect(fs.existsSync(appendedFilePath(null))).toBe(false);
2706
-
2707
- consoleErrorSpy.mockRestore();
2708
- processExitSpy.mockRestore();
2709
- });
2710
-
2711
- const runDelete = (): Promise<unknown> =>
2712
- program.parseAsync([
2713
- 'node',
2714
- 'test',
2715
- 'ownerCallFileDelete',
2716
- '--session',
2717
- sessionName,
2718
- '--inTmuxDataDir',
2719
- ownerCallDataDir,
2720
- ]);
2721
-
2722
- it('deletes the file of the session whichever project directory holds it', async () => {
2723
- const projects: InTmuxByHumanProjectData[] = [
2724
- { projectCode: 'umino', sessionIssueUrls: [issueUrl] },
2725
- ];
2726
- writeInTmuxByHumanData(projects);
2727
- await runAppend('2026-08-14T04:22:28Z');
2728
- const writtenFilePath = appendedFilePath(
2729
- projectCodeListing(projects, issueUrl),
2730
- );
2731
- expect(fs.existsSync(writtenFilePath)).toBe(true);
2732
-
2733
- await runDelete();
2734
-
2735
- expect(fs.existsSync(writtenFilePath)).toBe(false);
2736
- });
2737
-
2738
- it('deletes the file left behind under the project the session has since left', async () => {
2739
- writeInTmuxByHumanData([
2740
- { projectCode: 'umino', sessionIssueUrls: [issueUrl] },
2741
- ]);
2742
- await runAppend('2026-08-14T04:22:28Z');
2743
- writeInTmuxByHumanData([
2744
- { projectCode: 'umino', sessionIssueUrls: [] },
2745
- { projectCode: 'other', sessionIssueUrls: [issueUrl] },
2746
- ]);
2747
-
2748
- await runDelete();
2749
-
2750
- expect(fs.existsSync(appendedFilePath('umino'))).toBe(false);
2751
- });
2752
-
2753
- it('succeeds when the file to delete is already absent', async () => {
2754
- await expect(runDelete()).resolves.toBeDefined();
2755
- });
2756
- });
2757
-
2758
- describe('runCliProgram', () => {
2759
- const mockCheckUseCaseRejectingWith = (error: Error): void => {
2760
- const MockedCheckUseCase = jest.mocked(CheckIssueReviewReadinessUseCase);
2761
- MockedCheckUseCase.mockImplementation(function (
2762
- this: CheckIssueReviewReadinessUseCase,
2763
- ) {
2764
- this.run = jest.fn().mockRejectedValue(error);
2765
- return this;
2766
- });
2767
- };
2768
-
2769
- const checkIssueReviewReadinessArgv = [
2770
- 'node',
2771
- 'test',
2772
- 'checkIssueReviewReadiness',
2773
- '--configFilePath',
2774
- configFilePath,
2775
- '--issueUrl',
2776
- 'https://github.com/test/repo/issues/1',
2777
- ];
2778
-
2779
- it('should pass the rejection of an async action handler to the fatal error handler', async () => {
2780
- const thrownError = new Error('GitHub API failure');
2781
- mockCheckUseCaseRejectingWith(thrownError);
2782
- const handleFatalError = jest.fn();
2783
-
2784
- await runCliProgram(checkIssueReviewReadinessArgv, handleFatalError);
2785
-
2786
- expect(handleFatalError).toHaveBeenCalledTimes(1);
2787
- expect(handleFatalError).toHaveBeenCalledWith(thrownError);
2788
- });
2789
-
2790
- it('should not call the fatal error handler when the async action handler succeeds', async () => {
2791
- const mockRun = jest
2792
- .fn()
2793
- .mockResolvedValue({ reviewReady: true, rejections: [] });
2794
- const MockedCheckUseCase = jest.mocked(CheckIssueReviewReadinessUseCase);
2795
-
2796
- MockedCheckUseCase.mockImplementation(function (
2797
- this: CheckIssueReviewReadinessUseCase,
2798
- ) {
2799
- this.run = mockRun;
2800
- return this;
2801
- });
2802
- const stdoutSpy = jest
2803
- .spyOn(process.stdout, 'write')
2804
- .mockImplementation(() => true);
2805
- const handleFatalError = jest.fn();
2806
-
2807
- await runCliProgram(checkIssueReviewReadinessArgv, handleFatalError);
2808
-
2809
- expect(mockRun).toHaveBeenCalledTimes(1);
2810
- expect(handleFatalError).not.toHaveBeenCalled();
2811
-
2812
- stdoutSpy.mockRestore();
2813
- });
2814
- });
2815
-
2816
- describe('reportFatalErrorAndExit', () => {
2817
- it('should write the error to stderr and exit with code 1', () => {
2818
- const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation();
2819
- const processExitSpy = jest
2820
- .spyOn(process, 'exit')
2821
- .mockImplementation(() => {
2822
- throw new Error('process.exit called');
2823
- });
2824
- const thrownError = new Error('GitHub API failure');
2825
-
2826
- expect(() => reportFatalErrorAndExit(thrownError)).toThrow(
2827
- 'process.exit called',
2828
- );
2829
-
2830
- expect(consoleErrorSpy).toHaveBeenCalledWith(thrownError);
2831
- expect(processExitSpy).toHaveBeenCalledWith(1);
2832
-
2833
- consoleErrorSpy.mockRestore();
2834
- processExitSpy.mockRestore();
2835
- });
2836
- });
2837
- });