reviewflow 3.34.0 → 3.35.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (190) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/dist/dashboard/index.html +16 -2
  3. package/dist/dashboard/setup.html +17 -1
  4. package/dist/dashboard/styles.css +75 -0
  5. package/dist/main/routes.d.ts.map +1 -1
  6. package/dist/main/routes.js +56 -28
  7. package/dist/main/routes.js.map +1 -1
  8. package/dist/modules/platform-integration/interface-adapters/controllers/webhook/github.controller.d.ts +4 -0
  9. package/dist/modules/platform-integration/interface-adapters/controllers/webhook/github.controller.d.ts.map +1 -1
  10. package/dist/modules/platform-integration/interface-adapters/controllers/webhook/github.controller.js +54 -46
  11. package/dist/modules/platform-integration/interface-adapters/controllers/webhook/github.controller.js.map +1 -1
  12. package/dist/modules/platform-integration/interface-adapters/gateways/cli/noteCommentPost.github.cli.gateway.d.ts.map +1 -1
  13. package/dist/modules/platform-integration/interface-adapters/gateways/cli/noteCommentPost.github.cli.gateway.js +4 -1
  14. package/dist/modules/platform-integration/interface-adapters/gateways/cli/noteCommentPost.github.cli.gateway.js.map +1 -1
  15. package/dist/modules/platform-integration/interface-adapters/gateways/cli/noteCommentPost.gitlab.cli.gateway.d.ts.map +1 -1
  16. package/dist/modules/platform-integration/interface-adapters/gateways/cli/noteCommentPost.gitlab.cli.gateway.js +4 -1
  17. package/dist/modules/platform-integration/interface-adapters/gateways/cli/noteCommentPost.gitlab.cli.gateway.js.map +1 -1
  18. package/dist/modules/platform-integration/interface-adapters/gateways/threadFetch.gitlab.gateway.d.ts.map +1 -1
  19. package/dist/modules/platform-integration/interface-adapters/gateways/threadFetch.gitlab.gateway.js +3 -0
  20. package/dist/modules/platform-integration/interface-adapters/gateways/threadFetch.gitlab.gateway.js.map +1 -1
  21. package/dist/modules/platform-integration/services/scopedExecutorEnvironment.d.ts +1 -0
  22. package/dist/modules/platform-integration/services/scopedExecutorEnvironment.d.ts.map +1 -1
  23. package/dist/modules/platform-integration/services/scopedExecutorEnvironment.js +10 -3
  24. package/dist/modules/platform-integration/services/scopedExecutorEnvironment.js.map +1 -1
  25. package/dist/modules/review-execution/interface-adapters/controllers/http/pendingReviews.routes.d.ts.map +1 -1
  26. package/dist/modules/review-execution/interface-adapters/controllers/http/pendingReviews.routes.js +5 -1
  27. package/dist/modules/review-execution/interface-adapters/controllers/http/pendingReviews.routes.js.map +1 -1
  28. package/dist/modules/review-execution/interface-adapters/gateways/cli/reviewAction.github.cli.gateway.d.ts.map +1 -1
  29. package/dist/modules/review-execution/interface-adapters/gateways/cli/reviewAction.github.cli.gateway.js +6 -4
  30. package/dist/modules/review-execution/interface-adapters/gateways/cli/reviewAction.github.cli.gateway.js.map +1 -1
  31. package/dist/modules/review-execution/services/contextActionsExecutor.d.ts.map +1 -1
  32. package/dist/modules/review-execution/services/contextActionsExecutor.js +23 -1
  33. package/dist/modules/review-execution/services/contextActionsExecutor.js.map +1 -1
  34. package/dist/modules/review-execution/usecases/confirmPendingReview.usecase.d.ts +4 -0
  35. package/dist/modules/review-execution/usecases/confirmPendingReview.usecase.d.ts.map +1 -1
  36. package/dist/modules/review-execution/usecases/confirmPendingReview.usecase.js +6 -1
  37. package/dist/modules/review-execution/usecases/confirmPendingReview.usecase.js.map +1 -1
  38. package/dist/modules/setup-wizard/interface-adapters/controllers/http/setupWizard.routes.d.ts.map +1 -1
  39. package/dist/modules/setup-wizard/interface-adapters/controllers/http/setupWizard.routes.js +9 -0
  40. package/dist/modules/setup-wizard/interface-adapters/controllers/http/setupWizard.routes.js.map +1 -1
  41. package/dist/modules/setup-wizard/usecases/streamSetupRun.usecase.d.ts +14 -1
  42. package/dist/modules/setup-wizard/usecases/streamSetupRun.usecase.d.ts.map +1 -1
  43. package/dist/modules/setup-wizard/usecases/streamSetupRun.usecase.js +50 -6
  44. package/dist/modules/setup-wizard/usecases/streamSetupRun.usecase.js.map +1 -1
  45. package/dist/modules/tracking/interface-adapters/controllers/http/mrTrackingAdvanced.routes.d.ts.map +1 -1
  46. package/dist/modules/tracking/interface-adapters/controllers/http/mrTrackingAdvanced.routes.js +14 -6
  47. package/dist/modules/tracking/interface-adapters/controllers/http/mrTrackingAdvanced.routes.js.map +1 -1
  48. package/dist/tests/acceptance/174-semi-auto-review-trigger-mode.acceptance.test.js +1 -0
  49. package/dist/tests/acceptance/174-semi-auto-review-trigger-mode.acceptance.test.js.map +1 -1
  50. package/dist/tests/acceptance/202-confirm-pending-review-runs-review.acceptance.test.d.ts +2 -0
  51. package/dist/tests/acceptance/202-confirm-pending-review-runs-review.acceptance.test.d.ts.map +1 -0
  52. package/dist/tests/acceptance/202-confirm-pending-review-runs-review.acceptance.test.js +173 -0
  53. package/dist/tests/acceptance/202-confirm-pending-review-runs-review.acceptance.test.js.map +1 -0
  54. package/dist/tests/factories/pendingReviewRequest.factory.d.ts +1 -0
  55. package/dist/tests/factories/pendingReviewRequest.factory.d.ts.map +1 -1
  56. package/dist/tests/factories/pendingReviewRequest.factory.js +22 -0
  57. package/dist/tests/factories/pendingReviewRequest.factory.js.map +1 -1
  58. package/dist/tests/stubs/claudeSession.stub.d.ts +8 -0
  59. package/dist/tests/stubs/claudeSession.stub.d.ts.map +1 -1
  60. package/dist/tests/stubs/claudeSession.stub.js +24 -2
  61. package/dist/tests/stubs/claudeSession.stub.js.map +1 -1
  62. package/dist/tests/units/entities/insight/teamInsight.guard.test.d.ts +2 -0
  63. package/dist/tests/units/entities/insight/teamInsight.guard.test.d.ts.map +1 -0
  64. package/dist/tests/units/entities/insight/teamInsight.guard.test.js +45 -0
  65. package/dist/tests/units/entities/insight/teamInsight.guard.test.js.map +1 -0
  66. package/dist/tests/units/interface-adapters/controllers/http/stats.routes.test.d.ts +2 -0
  67. package/dist/tests/units/interface-adapters/controllers/http/stats.routes.test.d.ts.map +1 -0
  68. package/dist/tests/units/interface-adapters/controllers/http/stats.routes.test.js +208 -0
  69. package/dist/tests/units/interface-adapters/controllers/http/stats.routes.test.js.map +1 -0
  70. package/dist/tests/units/interface-adapters/controllers/mcp/addAction.handler.test.js +92 -0
  71. package/dist/tests/units/interface-adapters/controllers/mcp/addAction.handler.test.js.map +1 -1
  72. package/dist/tests/units/interface-adapters/controllers/webhook/github.controller.test.js +303 -0
  73. package/dist/tests/units/interface-adapters/controllers/webhook/github.controller.test.js.map +1 -1
  74. package/dist/tests/units/interface-adapters/controllers/webhook/gitlab.controller.test.js +359 -1
  75. package/dist/tests/units/interface-adapters/controllers/webhook/gitlab.controller.test.js.map +1 -1
  76. package/dist/tests/units/interface-adapters/gateways/cli/reviewAction.github.cli.gateway.test.js +10 -3
  77. package/dist/tests/units/interface-adapters/gateways/cli/reviewAction.github.cli.gateway.test.js.map +1 -1
  78. package/dist/tests/units/modules/claude-invocation/usecases/awaitSessionCompletion.usecase.test.js +66 -0
  79. package/dist/tests/units/modules/claude-invocation/usecases/awaitSessionCompletion.usecase.test.js.map +1 -1
  80. package/dist/tests/units/modules/claude-invocation/usecases/cleanupClaudeSession.usecase.test.js +62 -0
  81. package/dist/tests/units/modules/claude-invocation/usecases/cleanupClaudeSession.usecase.test.js.map +1 -1
  82. package/dist/tests/units/modules/cli-configuration/interface-adapters/controllers/http/cliStatus.routes.test.d.ts +2 -0
  83. package/dist/tests/units/modules/cli-configuration/interface-adapters/controllers/http/cliStatus.routes.test.d.ts.map +1 -0
  84. package/dist/tests/units/modules/cli-configuration/interface-adapters/controllers/http/cliStatus.routes.test.js +182 -0
  85. package/dist/tests/units/modules/cli-configuration/interface-adapters/controllers/http/cliStatus.routes.test.js.map +1 -0
  86. package/dist/tests/units/modules/cli-configuration/interface-adapters/controllers/http/projectConfig.routes.test.js +225 -0
  87. package/dist/tests/units/modules/cli-configuration/interface-adapters/controllers/http/projectConfig.routes.test.js.map +1 -1
  88. package/dist/tests/units/modules/ember-chat/gateways/emberAnswerTransport.claude.gateway.test.d.ts +2 -0
  89. package/dist/tests/units/modules/ember-chat/gateways/emberAnswerTransport.claude.gateway.test.d.ts.map +1 -0
  90. package/dist/tests/units/modules/ember-chat/gateways/emberAnswerTransport.claude.gateway.test.js +171 -0
  91. package/dist/tests/units/modules/ember-chat/gateways/emberAnswerTransport.claude.gateway.test.js.map +1 -0
  92. package/dist/tests/units/modules/platform-integration/controllers/githubProcessorProvenance.test.d.ts +2 -0
  93. package/dist/tests/units/modules/platform-integration/controllers/githubProcessorProvenance.test.d.ts.map +1 -0
  94. package/dist/tests/units/modules/platform-integration/controllers/githubProcessorProvenance.test.js +70 -0
  95. package/dist/tests/units/modules/platform-integration/controllers/githubProcessorProvenance.test.js.map +1 -0
  96. package/dist/tests/units/modules/platform-integration/entities/transport/cidr.test.d.ts +2 -0
  97. package/dist/tests/units/modules/platform-integration/entities/transport/cidr.test.d.ts.map +1 -0
  98. package/dist/tests/units/modules/platform-integration/entities/transport/cidr.test.js +51 -0
  99. package/dist/tests/units/modules/platform-integration/entities/transport/cidr.test.js.map +1 -0
  100. package/dist/tests/units/modules/platform-integration/gateways/scopedGitLabExecutor.test.js +4 -0
  101. package/dist/tests/units/modules/platform-integration/gateways/scopedGitLabExecutor.test.js.map +1 -1
  102. package/dist/tests/units/modules/platform-integration/interface-adapters/gateways/cli/noteCommentPost.cli.shellSafety.test.d.ts +2 -0
  103. package/dist/tests/units/modules/platform-integration/interface-adapters/gateways/cli/noteCommentPost.cli.shellSafety.test.d.ts.map +1 -0
  104. package/dist/tests/units/modules/platform-integration/interface-adapters/gateways/cli/noteCommentPost.cli.shellSafety.test.js +39 -0
  105. package/dist/tests/units/modules/platform-integration/interface-adapters/gateways/cli/noteCommentPost.cli.shellSafety.test.js.map +1 -0
  106. package/dist/tests/units/modules/platform-integration/services/scopedExecutorEnvironment.test.js +15 -2
  107. package/dist/tests/units/modules/platform-integration/services/scopedExecutorEnvironment.test.js.map +1 -1
  108. package/dist/tests/units/modules/review-execution/entities/reviewRequest/reviewRequest.guard.test.d.ts +2 -0
  109. package/dist/tests/units/modules/review-execution/entities/reviewRequest/reviewRequest.guard.test.d.ts.map +1 -0
  110. package/dist/tests/units/modules/review-execution/entities/reviewRequest/reviewRequest.guard.test.js +34 -0
  111. package/dist/tests/units/modules/review-execution/entities/reviewRequest/reviewRequest.guard.test.js.map +1 -0
  112. package/dist/tests/units/modules/review-execution/interface-adapters/controllers/http/pendingReviews.routes.test.js +21 -2
  113. package/dist/tests/units/modules/review-execution/interface-adapters/controllers/http/pendingReviews.routes.test.js.map +1 -1
  114. package/dist/tests/units/modules/review-execution/interface-adapters/controllers/http/reviews.routes.test.d.ts +2 -0
  115. package/dist/tests/units/modules/review-execution/interface-adapters/controllers/http/reviews.routes.test.d.ts.map +1 -0
  116. package/dist/tests/units/modules/review-execution/interface-adapters/controllers/http/reviews.routes.test.js +210 -0
  117. package/dist/tests/units/modules/review-execution/interface-adapters/controllers/http/reviews.routes.test.js.map +1 -0
  118. package/dist/tests/units/modules/review-execution/interface-adapters/gateways/fileSystem/reviewFile.fileSystem.test.d.ts +2 -0
  119. package/dist/tests/units/modules/review-execution/interface-adapters/gateways/fileSystem/reviewFile.fileSystem.test.d.ts.map +1 -0
  120. package/dist/tests/units/modules/review-execution/interface-adapters/gateways/fileSystem/reviewFile.fileSystem.test.js +103 -0
  121. package/dist/tests/units/modules/review-execution/interface-adapters/gateways/fileSystem/reviewFile.fileSystem.test.js.map +1 -0
  122. package/dist/tests/units/modules/review-execution/usecases/confirmPendingReview.usecase.test.js +25 -0
  123. package/dist/tests/units/modules/review-execution/usecases/confirmPendingReview.usecase.test.js.map +1 -1
  124. package/dist/tests/units/modules/setup-wizard/gateways/serverConfig.fileSystem.gateway.test.d.ts +2 -0
  125. package/dist/tests/units/modules/setup-wizard/gateways/serverConfig.fileSystem.gateway.test.d.ts.map +1 -0
  126. package/dist/tests/units/modules/setup-wizard/gateways/serverConfig.fileSystem.gateway.test.js +98 -0
  127. package/dist/tests/units/modules/setup-wizard/gateways/serverConfig.fileSystem.gateway.test.js.map +1 -0
  128. package/dist/tests/units/modules/setup-wizard/interface-adapters/controllers/http/setupWizard.routes.test.js +12 -0
  129. package/dist/tests/units/modules/setup-wizard/interface-adapters/controllers/http/setupWizard.routes.test.js.map +1 -1
  130. package/dist/tests/units/modules/setup-wizard/interface-adapters/gateways/skillTemplate.fileSystem.gateway.test.d.ts +2 -0
  131. package/dist/tests/units/modules/setup-wizard/interface-adapters/gateways/skillTemplate.fileSystem.gateway.test.d.ts.map +1 -0
  132. package/dist/tests/units/modules/setup-wizard/interface-adapters/gateways/skillTemplate.fileSystem.gateway.test.js +51 -0
  133. package/dist/tests/units/modules/setup-wizard/interface-adapters/gateways/skillTemplate.fileSystem.gateway.test.js.map +1 -0
  134. package/dist/tests/units/modules/setup-wizard/interface-adapters/gateways/validation.adapter.gateway.test.d.ts +2 -0
  135. package/dist/tests/units/modules/setup-wizard/interface-adapters/gateways/validation.adapter.gateway.test.d.ts.map +1 -0
  136. package/dist/tests/units/modules/setup-wizard/interface-adapters/gateways/validation.adapter.gateway.test.js +70 -0
  137. package/dist/tests/units/modules/setup-wizard/interface-adapters/gateways/validation.adapter.gateway.test.js.map +1 -0
  138. package/dist/tests/units/modules/setup-wizard/usecases/streamSetupRun.usecase.test.js +60 -1
  139. package/dist/tests/units/modules/setup-wizard/usecases/streamSetupRun.usecase.test.js.map +1 -1
  140. package/dist/tests/units/modules/statistics-insights/entities/insight/developerInsight.guard.test.d.ts +2 -0
  141. package/dist/tests/units/modules/statistics-insights/entities/insight/developerInsight.guard.test.d.ts.map +1 -0
  142. package/dist/tests/units/modules/statistics-insights/entities/insight/developerInsight.guard.test.js +49 -0
  143. package/dist/tests/units/modules/statistics-insights/entities/insight/developerInsight.guard.test.js.map +1 -0
  144. package/dist/tests/units/modules/statistics-insights/entities/insight/persistedInsightsData.guard.test.d.ts +2 -0
  145. package/dist/tests/units/modules/statistics-insights/entities/insight/persistedInsightsData.guard.test.d.ts.map +1 -0
  146. package/dist/tests/units/modules/statistics-insights/entities/insight/persistedInsightsData.guard.test.js +29 -0
  147. package/dist/tests/units/modules/statistics-insights/entities/insight/persistedInsightsData.guard.test.js.map +1 -0
  148. package/dist/tests/units/modules/statistics-insights/gateways/aiInsightsSession.claude.gateway.test.d.ts +2 -0
  149. package/dist/tests/units/modules/statistics-insights/gateways/aiInsightsSession.claude.gateway.test.d.ts.map +1 -0
  150. package/dist/tests/units/modules/statistics-insights/gateways/aiInsightsSession.claude.gateway.test.js +63 -0
  151. package/dist/tests/units/modules/statistics-insights/gateways/aiInsightsSession.claude.gateway.test.js.map +1 -0
  152. package/dist/tests/units/modules/statistics-insights/gateways/stats.fileSystem.test.d.ts +2 -0
  153. package/dist/tests/units/modules/statistics-insights/gateways/stats.fileSystem.test.d.ts.map +1 -0
  154. package/dist/tests/units/modules/statistics-insights/gateways/stats.fileSystem.test.js +73 -0
  155. package/dist/tests/units/modules/statistics-insights/gateways/stats.fileSystem.test.js.map +1 -0
  156. package/dist/tests/units/modules/statistics-insights/interface-adapters/presenters/overview.presenter.test.js +99 -0
  157. package/dist/tests/units/modules/statistics-insights/interface-adapters/presenters/overview.presenter.test.js.map +1 -1
  158. package/dist/tests/units/modules/supervisor-management/gateways/supervisorLock.fileSystem.gateway.test.js +60 -2
  159. package/dist/tests/units/modules/supervisor-management/gateways/supervisorLock.fileSystem.gateway.test.js.map +1 -1
  160. package/dist/tests/units/modules/tracking/interface-adapters/controllers/http/mrTrackingAdvanced.routes.test.js +499 -18
  161. package/dist/tests/units/modules/tracking/interface-adapters/controllers/http/mrTrackingAdvanced.routes.test.js.map +1 -1
  162. package/dist/tests/units/modules/tracking/interface-adapters/gateways/fileSystem/reviewRequestTracking.fileSystem.test.d.ts +2 -0
  163. package/dist/tests/units/modules/tracking/interface-adapters/gateways/fileSystem/reviewRequestTracking.fileSystem.test.d.ts.map +1 -0
  164. package/dist/tests/units/modules/tracking/interface-adapters/gateways/fileSystem/reviewRequestTracking.fileSystem.test.js +273 -0
  165. package/dist/tests/units/modules/tracking/interface-adapters/gateways/fileSystem/reviewRequestTracking.fileSystem.test.js.map +1 -0
  166. package/dist/tests/units/modules/worktree-management/entities/worktree/worktree.test.js +47 -1
  167. package/dist/tests/units/modules/worktree-management/entities/worktree/worktree.test.js.map +1 -1
  168. package/dist/tests/units/modules/worktree-management/usecases/sweepStaleWorktrees.usecase.test.js +98 -0
  169. package/dist/tests/units/modules/worktree-management/usecases/sweepStaleWorktrees.usecase.test.js.map +1 -1
  170. package/dist/tests/units/services/contextActionsExecutor.test.js +67 -0
  171. package/dist/tests/units/services/contextActionsExecutor.test.js.map +1 -1
  172. package/dist/tests/units/services/statsService.branches.test.d.ts +2 -0
  173. package/dist/tests/units/services/statsService.branches.test.d.ts.map +1 -0
  174. package/dist/tests/units/services/statsService.branches.test.js +266 -0
  175. package/dist/tests/units/services/statsService.branches.test.js.map +1 -0
  176. package/dist/tests/units/shared/services/logFileReader.test.d.ts +2 -0
  177. package/dist/tests/units/shared/services/logFileReader.test.d.ts.map +1 -0
  178. package/dist/tests/units/shared/services/logFileReader.test.js +70 -0
  179. package/dist/tests/units/shared/services/logFileReader.test.js.map +1 -0
  180. package/dist/tests/units/usecases/insights/buildAiInsightsPrompt.test.js +206 -0
  181. package/dist/tests/units/usecases/insights/buildAiInsightsPrompt.test.js.map +1 -1
  182. package/dist/tests/units/usecases/insights/computeDeveloperInsights.usecase.test.js +40 -0
  183. package/dist/tests/units/usecases/insights/computeDeveloperInsights.usecase.test.js.map +1 -1
  184. package/dist/tests/units/usecases/insights/computeInsightsWithPersistence.usecase.test.js +204 -0
  185. package/dist/tests/units/usecases/insights/computeInsightsWithPersistence.usecase.test.js.map +1 -1
  186. package/dist/tests/units/usecases/insights/getInsightsWithAiStatus.usecase.test.js +57 -0
  187. package/dist/tests/units/usecases/insights/getInsightsWithAiStatus.usecase.test.js.map +1 -1
  188. package/dist/tests/units/usecases/insights/insightLevelComputation.service.test.js +391 -1
  189. package/dist/tests/units/usecases/insights/insightLevelComputation.service.test.js.map +1 -1
  190. package/package.json +4 -1
@@ -35,6 +35,72 @@ describe('awaitSessionCompletion use case', () => {
35
35
  expect(completion.source).toBe('polling');
36
36
  expect(completion.outcome).toBe('completed');
37
37
  });
38
+ it('maps a stopped agent status to a stopped polling outcome', async () => {
39
+ const session = ClaudeSessionFactory.create({ sessionId: parseSessionId('stop0003') });
40
+ const bridge = new StubMcpCompletionBridge();
41
+ const sessionGateway = new StubClaudeSessionGateway();
42
+ sessionGateway.scheduleAgentCompletion('stop0003', 'stopped', 1);
43
+ const promise = awaitSessionCompletion({ session, timeoutMs: 60_000, pollIntervalMs: 30_000 }, { sessionGateway, completionBridge: bridge, now: () => new Date('2026-05-22T10:00:00Z') });
44
+ await vi.advanceTimersByTimeAsync(30_000);
45
+ const completion = await promise;
46
+ expect(completion.source).toBe('polling');
47
+ expect(completion.outcome).toBe('stopped');
48
+ expect(completion.reason).toBeNull();
49
+ });
50
+ it('maps a failed agent status to a failed polling outcome', async () => {
51
+ const session = ClaudeSessionFactory.create({ sessionId: parseSessionId('fail0004') });
52
+ const bridge = new StubMcpCompletionBridge();
53
+ const sessionGateway = new StubClaudeSessionGateway();
54
+ sessionGateway.scheduleAgentCompletion('fail0004', 'failed', 1);
55
+ const promise = awaitSessionCompletion({ session, timeoutMs: 60_000, pollIntervalMs: 30_000 }, { sessionGateway, completionBridge: bridge, now: () => new Date('2026-05-22T10:00:00Z') });
56
+ await vi.advanceTimersByTimeAsync(30_000);
57
+ const completion = await promise;
58
+ expect(completion.source).toBe('polling');
59
+ expect(completion.outcome).toBe('failed');
60
+ expect(completion.reason).toBeNull();
61
+ });
62
+ it('keeps polling while the agent is still running, then settles when it completes', async () => {
63
+ const session = ClaudeSessionFactory.create({ sessionId: parseSessionId('run00005') });
64
+ const bridge = new StubMcpCompletionBridge();
65
+ class RunningThenDoneSessionGateway extends StubClaudeSessionGateway {
66
+ async listAgents() {
67
+ const call = this.listAgentsCallCount + 1;
68
+ await super.listAgents();
69
+ const status = call === 1 ? 'running' : 'completed';
70
+ return [{ sessionId: session.sessionId, status }];
71
+ }
72
+ }
73
+ const sessionGateway = new RunningThenDoneSessionGateway();
74
+ const promise = awaitSessionCompletion({ session, timeoutMs: 60_000, pollIntervalMs: 10_000 }, { sessionGateway, completionBridge: bridge, now: () => new Date('2026-05-22T10:00:00Z') });
75
+ await vi.advanceTimersByTimeAsync(10_000);
76
+ expect(sessionGateway.listAgentsCallCount).toBe(1);
77
+ await vi.advanceTimersByTimeAsync(10_000);
78
+ const completion = await promise;
79
+ expect(completion.source).toBe('polling');
80
+ expect(completion.outcome).toBe('completed');
81
+ });
82
+ it('treats a listAgents error as non-fatal and retries on the next tick', async () => {
83
+ const session = ClaudeSessionFactory.create({ sessionId: parseSessionId('errr0006') });
84
+ const bridge = new StubMcpCompletionBridge();
85
+ class ThrowOnceSessionGateway extends StubClaudeSessionGateway {
86
+ hasThrown = false;
87
+ async listAgents() {
88
+ if (!this.hasThrown) {
89
+ this.hasThrown = true;
90
+ throw new Error('daemon unreachable');
91
+ }
92
+ return super.listAgents();
93
+ }
94
+ }
95
+ const sessionGateway = new ThrowOnceSessionGateway();
96
+ sessionGateway.scheduleAgentCompletion('errr0006', 'completed', 1);
97
+ const promise = awaitSessionCompletion({ session, timeoutMs: 60_000, pollIntervalMs: 10_000 }, { sessionGateway, completionBridge: bridge, now: () => new Date('2026-05-22T10:00:00Z') });
98
+ await vi.advanceTimersByTimeAsync(10_000);
99
+ await vi.advanceTimersByTimeAsync(10_000);
100
+ const completion = await promise;
101
+ expect(completion.source).toBe('polling');
102
+ expect(completion.outcome).toBe('completed');
103
+ });
38
104
  it('returns timeout when no signal arrives within the timeout window', async () => {
39
105
  const session = ClaudeSessionFactory.create({ sessionId: parseSessionId('time0002') });
40
106
  const bridge = new StubMcpCompletionBridge();
@@ -1 +1 @@
1
- {"version":3,"file":"awaitSessionCompletion.usecase.test.js","sourceRoot":"","sources":["../../../../../../src/tests/units/modules/claude-invocation/usecases/awaitSessionCompletion.usecase.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AAC9D,OAAO,EAAE,sBAAsB,EAAE,MAAM,wEAAwE,CAAC;AAChH,OAAO,EAAE,uBAAuB,EAAE,MAAM,qCAAqC,CAAC;AAC9E,OAAO,EAAE,wBAAwB,EAAE,MAAM,qCAAqC,CAAC;AAC/E,OAAO,EAAE,oBAAoB,EAAE,MAAM,4CAA4C,CAAC;AAClF,OAAO,EAAE,cAAc,EAAE,MAAM,4EAA4E,CAAC;AAE5G,QAAQ,CAAC,iCAAiC,EAAE,GAAG,EAAE;IAC/C,UAAU,CAAC,GAAG,EAAE;QACd,EAAE,CAAC,aAAa,EAAE,CAAC;QACnB,EAAE,CAAC,aAAa,CAAC,IAAI,IAAI,CAAC,sBAAsB,CAAC,CAAC,CAAC;IACrD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,kDAAkD,EAAE,KAAK,IAAI,EAAE;QAChE,MAAM,OAAO,GAAG,oBAAoB,CAAC,MAAM,CAAC,EAAE,SAAS,EAAE,cAAc,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC;QACvF,MAAM,MAAM,GAAG,IAAI,uBAAuB,EAAE,CAAC;QAC7C,MAAM,cAAc,GAAG,IAAI,wBAAwB,EAAE,CAAC;QACtD,MAAM,CAAC,kBAAkB,CAAC,OAAO,CAAC,KAAK,EAAE;YACvC,MAAM,EAAE,KAAK;YACb,OAAO,EAAE,WAAW;YACpB,MAAM,EAAE,IAAI;SACb,CAAC,CAAC;QAEH,MAAM,OAAO,GAAG,sBAAsB,CACpC,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,EAAE,EACtD,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,IAAI,IAAI,CAAC,sBAAsB,CAAC,EAAE,CAC1F,CAAC;QACF,MAAM,EAAE,CAAC,iBAAiB,EAAE,CAAC;QAC7B,MAAM,UAAU,GAAG,MAAM,OAAO,CAAC;QAEjC,MAAM,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACtC,MAAM,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;IAC/C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,sDAAsD,EAAE,KAAK,IAAI,EAAE;QACpE,MAAM,OAAO,GAAG,oBAAoB,CAAC,MAAM,CAAC,EAAE,SAAS,EAAE,cAAc,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC;QACvF,MAAM,MAAM,GAAG,IAAI,uBAAuB,EAAE,CAAC;QAC7C,MAAM,cAAc,GAAG,IAAI,wBAAwB,EAAE,CAAC;QACtD,cAAc,CAAC,uBAAuB,CAAC,UAAU,EAAE,WAAW,EAAE,CAAC,CAAC,CAAC;QAEnE,MAAM,OAAO,GAAG,sBAAsB,CACpC,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,EAAE,EACtD,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,IAAI,IAAI,CAAC,sBAAsB,CAAC,EAAE,CAC1F,CAAC;QACF,MAAM,EAAE,CAAC,wBAAwB,CAAC,MAAM,CAAC,CAAC;QAC1C,MAAM,UAAU,GAAG,MAAM,OAAO,CAAC;QAEjC,MAAM,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QAC1C,MAAM,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;IAC/C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,kEAAkE,EAAE,KAAK,IAAI,EAAE;QAChF,MAAM,OAAO,GAAG,oBAAoB,CAAC,MAAM,CAAC,EAAE,SAAS,EAAE,cAAc,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC;QACvF,MAAM,MAAM,GAAG,IAAI,uBAAuB,EAAE,CAAC;QAC7C,MAAM,cAAc,GAAG,IAAI,wBAAwB,EAAE,CAAC;QACtD,IAAI,KAAK,GAAG,IAAI,IAAI,CAAC,sBAAsB,CAAC,CAAC,OAAO,EAAE,CAAC;QACvD,MAAM,GAAG,GAAG,GAAS,EAAE,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,CAAC;QAExC,MAAM,OAAO,GAAG,sBAAsB,CACpC,EAAE,OAAO,EAAE,SAAS,EAAE,EAAE,GAAG,MAAM,EAAE,cAAc,EAAE,MAAM,EAAE,EAC3D,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,EAAE,GAAG,EAAE,CAClD,CAAC;QACF,KAAK,IAAI,EAAE,GAAG,MAAM,CAAC;QACrB,MAAM,EAAE,CAAC,wBAAwB,CAAC,EAAE,GAAG,MAAM,CAAC,CAAC;QAC/C,MAAM,UAAU,GAAG,MAAM,OAAO,CAAC;QAEjC,MAAM,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QAC1C,MAAM,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAC1C,MAAM,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IAC5C,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
1
+ {"version":3,"file":"awaitSessionCompletion.usecase.test.js","sourceRoot":"","sources":["../../../../../../src/tests/units/modules/claude-invocation/usecases/awaitSessionCompletion.usecase.test.ts"],"names":[],"mappings":"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"}
@@ -13,5 +13,67 @@ describe('cleanupClaudeSession use case', () => {
13
13
  expect(sessionGateway.stopCalls).toContain(sessionId);
14
14
  expect(sessionGateway.removeCalls).toContain(sessionId);
15
15
  });
16
+ it('records a stop warning when stop fails with a warning', async () => {
17
+ const sessionGateway = new StubClaudeSessionGateway();
18
+ sessionGateway.setStopResult({ success: false, warning: 'not running' });
19
+ const sessionId = parseSessionId('clean002');
20
+ const result = await cleanupClaudeSession({ sessionId }, { sessionGateway });
21
+ expect(result.stopped).toBe(false);
22
+ expect(result.removed).toBe(true);
23
+ expect(result.warnings).toEqual(['stop: not running']);
24
+ });
25
+ it('records a remove warning when remove fails with a warning', async () => {
26
+ const sessionGateway = new StubClaudeSessionGateway();
27
+ sessionGateway.setRemoveResult({ success: false, warning: 'still present' });
28
+ const sessionId = parseSessionId('clean003');
29
+ const result = await cleanupClaudeSession({ sessionId }, { sessionGateway });
30
+ expect(result.stopped).toBe(true);
31
+ expect(result.removed).toBe(false);
32
+ expect(result.warnings).toEqual(['remove: still present']);
33
+ });
34
+ it('does not record a warning when a step fails without a warning message', async () => {
35
+ const sessionGateway = new StubClaudeSessionGateway();
36
+ sessionGateway.setStopResult({ success: false, warning: null });
37
+ const sessionId = parseSessionId('clean004');
38
+ const result = await cleanupClaudeSession({ sessionId }, { sessionGateway });
39
+ expect(result.stopped).toBe(false);
40
+ expect(result.removed).toBe(true);
41
+ expect(result.warnings).toEqual([]);
42
+ });
43
+ it('captures the message when stop throws an Error', async () => {
44
+ const sessionGateway = new StubClaudeSessionGateway();
45
+ sessionGateway.setStopError(new Error('daemon unreachable'));
46
+ const sessionId = parseSessionId('clean005');
47
+ const result = await cleanupClaudeSession({ sessionId }, { sessionGateway });
48
+ expect(result.stopped).toBe(false);
49
+ expect(result.removed).toBe(true);
50
+ expect(result.warnings).toEqual([
51
+ 'stop failed: daemon unreachable',
52
+ 'stop: daemon unreachable',
53
+ ]);
54
+ });
55
+ it('stringifies the value when remove throws a non-Error', async () => {
56
+ const sessionGateway = new StubClaudeSessionGateway();
57
+ sessionGateway.setRemoveError('boom');
58
+ const sessionId = parseSessionId('clean006');
59
+ const result = await cleanupClaudeSession({ sessionId }, { sessionGateway });
60
+ expect(result.stopped).toBe(true);
61
+ expect(result.removed).toBe(false);
62
+ expect(result.warnings).toEqual(['remove failed: boom', 'remove: boom']);
63
+ });
64
+ it('accumulates warnings from both stop and remove failures', async () => {
65
+ const sessionGateway = new StubClaudeSessionGateway();
66
+ sessionGateway.setStopError(new Error('stop blew up'));
67
+ sessionGateway.setRemoveResult({ success: false, warning: 'cannot delete' });
68
+ const sessionId = parseSessionId('clean007');
69
+ const result = await cleanupClaudeSession({ sessionId }, { sessionGateway });
70
+ expect(result.stopped).toBe(false);
71
+ expect(result.removed).toBe(false);
72
+ expect(result.warnings).toEqual([
73
+ 'stop failed: stop blew up',
74
+ 'stop: stop blew up',
75
+ 'remove: cannot delete',
76
+ ]);
77
+ });
16
78
  });
17
79
  //# sourceMappingURL=cleanupClaudeSession.usecase.test.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"cleanupClaudeSession.usecase.test.js","sourceRoot":"","sources":["../../../../../../src/tests/units/modules/claude-invocation/usecases/cleanupClaudeSession.usecase.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,oBAAoB,EAAE,MAAM,sEAAsE,CAAC;AAC5G,OAAO,EAAE,wBAAwB,EAAE,MAAM,qCAAqC,CAAC;AAC/E,OAAO,EAAE,cAAc,EAAE,MAAM,4EAA4E,CAAC;AAE5G,QAAQ,CAAC,+BAA+B,EAAE,GAAG,EAAE;IAC7C,EAAE,CAAC,iDAAiD,EAAE,KAAK,IAAI,EAAE;QAC/D,MAAM,cAAc,GAAG,IAAI,wBAAwB,EAAE,CAAC;QACtD,MAAM,SAAS,GAAG,cAAc,CAAC,UAAU,CAAC,CAAC;QAE7C,MAAM,MAAM,GAAG,MAAM,oBAAoB,CAAC,EAAE,SAAS,EAAE,EAAE,EAAE,cAAc,EAAE,CAAC,CAAC;QAE7E,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAClC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAClC,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QACpC,MAAM,CAAC,cAAc,CAAC,SAAS,CAAC,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC;QACtD,MAAM,CAAC,cAAc,CAAC,WAAW,CAAC,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC;IAC1D,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
1
+ {"version":3,"file":"cleanupClaudeSession.usecase.test.js","sourceRoot":"","sources":["../../../../../../src/tests/units/modules/claude-invocation/usecases/cleanupClaudeSession.usecase.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=cliStatus.routes.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cliStatus.routes.test.d.ts","sourceRoot":"","sources":["../../../../../../../../src/tests/units/modules/cli-configuration/interface-adapters/controllers/http/cliStatus.routes.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,182 @@
1
+ import { describe, it, expect, beforeEach, vi } from 'vitest';
2
+ import { EventEmitter } from 'node:events';
3
+ import Fastify from 'fastify';
4
+ class FakeChildProcess extends EventEmitter {
5
+ stdout = new EventEmitter();
6
+ stderr = new EventEmitter();
7
+ emitStdout(data) {
8
+ this.stdout.emit('data', Buffer.from(data));
9
+ }
10
+ emitStderr(data) {
11
+ this.stderr.emit('data', Buffer.from(data));
12
+ }
13
+ }
14
+ let currentChild;
15
+ let lastCommand;
16
+ vi.mock('node:child_process', () => ({
17
+ spawn: (command) => {
18
+ lastCommand = command;
19
+ return currentChild;
20
+ },
21
+ }));
22
+ vi.mock('@/shared/services/claudePathResolver.js', () => ({
23
+ resolveClaudePath: () => '/usr/bin/claude',
24
+ }));
25
+ const { cliStatusRoutes } = await import('../../../../../../../modules/cli-configuration/interface-adapters/controllers/http/cliStatus.routes.js');
26
+ describe('cliStatus routes', () => {
27
+ let app;
28
+ beforeEach(async () => {
29
+ app = Fastify();
30
+ await app.register(cliStatusRoutes);
31
+ await app.ready();
32
+ currentChild = new FakeChildProcess();
33
+ });
34
+ function injectAfter(url, drive) {
35
+ const responsePromise = app.inject({ method: 'GET', url });
36
+ setImmediate(() => drive(currentChild));
37
+ return responsePromise;
38
+ }
39
+ describe('GET /api/claude/status', () => {
40
+ it('should report available when version is returned with exit code 0', async () => {
41
+ const response = await injectAfter('/api/claude/status', (child) => {
42
+ child.emitStdout('1.2.3');
43
+ child.emit('close', 0);
44
+ });
45
+ expect(lastCommand).toBe('/usr/bin/claude');
46
+ const body = response.json();
47
+ expect(body.available).toBe(true);
48
+ expect(body.version).toBe('1.2.3');
49
+ expect(body.message).toBe('Claude CLI operational');
50
+ expect(typeof body.duration).toBe('number');
51
+ });
52
+ it('should report unavailable with the error message on spawn error', async () => {
53
+ const response = await injectAfter('/api/claude/status', (child) => {
54
+ child.emit('error', new Error('spawn ENOENT'));
55
+ });
56
+ const body = response.json();
57
+ expect(body.available).toBe(false);
58
+ expect(body.error).toBe('spawn ENOENT');
59
+ expect(body.message).toBe('Claude CLI not installed or not accessible');
60
+ });
61
+ it('should report authentication message when stderr mentions not logged in', async () => {
62
+ const response = await injectAfter('/api/claude/status', (child) => {
63
+ child.emitStderr('error: not logged in');
64
+ child.emit('close', 1);
65
+ });
66
+ const body = response.json();
67
+ expect(body.available).toBe(false);
68
+ expect(body.exitCode).toBe(1);
69
+ expect(body.stderr).toBe('error: not logged in');
70
+ expect(body.message).toBe('Not authenticated - run "claude login"');
71
+ });
72
+ it('should report generic CLI error when exit code is non-zero without auth hint', async () => {
73
+ const response = await injectAfter('/api/claude/status', (child) => {
74
+ child.emitStderr('boom');
75
+ child.emit('close', 2);
76
+ });
77
+ const body = response.json();
78
+ expect(body.available).toBe(false);
79
+ expect(body.exitCode).toBe(2);
80
+ expect(body.message).toBe('Claude CLI error');
81
+ });
82
+ it('should report error when exit code is 0 but stdout is empty', async () => {
83
+ const response = await injectAfter('/api/claude/status', (child) => {
84
+ child.emit('close', 0);
85
+ });
86
+ const body = response.json();
87
+ expect(body.available).toBe(false);
88
+ expect(body.message).toBe('Claude CLI error');
89
+ });
90
+ });
91
+ describe('GET /api/gitlab/status', () => {
92
+ it('should spawn glab and report authenticated with valid JSON user', async () => {
93
+ const response = await injectAfter('/api/gitlab/status', (child) => {
94
+ child.emitStdout(JSON.stringify({ username: 'octocat' }));
95
+ child.emit('close', 0);
96
+ });
97
+ expect(lastCommand).toBe('glab');
98
+ const body = response.json();
99
+ expect(body.available).toBe(true);
100
+ expect(body.authenticated).toBe(true);
101
+ expect(body.username).toBe('octocat');
102
+ expect(body.message).toBe('GitLab CLI operational');
103
+ });
104
+ it('should report invalid response when stdout is not valid JSON', async () => {
105
+ const response = await injectAfter('/api/gitlab/status', (child) => {
106
+ child.emitStdout('not-json');
107
+ child.emit('close', 0);
108
+ });
109
+ const body = response.json();
110
+ expect(body.available).toBe(true);
111
+ expect(body.authenticated).toBe(false);
112
+ expect(body.message).toBe('Invalid GitLab response');
113
+ });
114
+ it('should report not installed on spawn error', async () => {
115
+ const response = await injectAfter('/api/gitlab/status', (child) => {
116
+ child.emit('error', new Error('glab missing'));
117
+ });
118
+ const body = response.json();
119
+ expect(body.available).toBe(false);
120
+ expect(body.authenticated).toBe(false);
121
+ expect(body.error).toBe('glab missing');
122
+ expect(body.message).toBe('GitLab CLI (glab) not installed');
123
+ expect(body.command).toBe('sudo apt install glab');
124
+ });
125
+ it('should report not authenticated when exit code is non-zero', async () => {
126
+ const response = await injectAfter('/api/gitlab/status', (child) => {
127
+ child.emitStderr('unauthorized');
128
+ child.emit('close', 1);
129
+ });
130
+ const body = response.json();
131
+ expect(body.available).toBe(true);
132
+ expect(body.authenticated).toBe(false);
133
+ expect(body.message).toBe('Not authenticated to GitLab');
134
+ });
135
+ });
136
+ describe('GET /api/github/status', () => {
137
+ it('should spawn gh and report authenticated with valid JSON user', async () => {
138
+ const response = await injectAfter('/api/github/status', (child) => {
139
+ child.emitStdout(JSON.stringify({ login: 'monalisa' }));
140
+ child.emit('close', 0);
141
+ });
142
+ expect(lastCommand).toBe('gh');
143
+ const body = response.json();
144
+ expect(body.available).toBe(true);
145
+ expect(body.authenticated).toBe(true);
146
+ expect(body.username).toBe('monalisa');
147
+ expect(body.message).toBe('GitHub CLI operational');
148
+ });
149
+ it('should report invalid response when stdout is not valid JSON', async () => {
150
+ const response = await injectAfter('/api/github/status', (child) => {
151
+ child.emitStdout('}{');
152
+ child.emit('close', 0);
153
+ });
154
+ const body = response.json();
155
+ expect(body.available).toBe(true);
156
+ expect(body.authenticated).toBe(false);
157
+ expect(body.message).toBe('Invalid GitHub response');
158
+ });
159
+ it('should report not installed on spawn error', async () => {
160
+ const response = await injectAfter('/api/github/status', (child) => {
161
+ child.emit('error', new Error('gh missing'));
162
+ });
163
+ const body = response.json();
164
+ expect(body.available).toBe(false);
165
+ expect(body.authenticated).toBe(false);
166
+ expect(body.error).toBe('gh missing');
167
+ expect(body.message).toBe('GitHub CLI (gh) not installed');
168
+ expect(body.command).toBe('sudo apt install gh');
169
+ });
170
+ it('should report not authenticated when exit code is non-zero', async () => {
171
+ const response = await injectAfter('/api/github/status', (child) => {
172
+ child.emitStderr('bad creds');
173
+ child.emit('close', 1);
174
+ });
175
+ const body = response.json();
176
+ expect(body.available).toBe(true);
177
+ expect(body.authenticated).toBe(false);
178
+ expect(body.message).toBe('Not authenticated - run: gh auth login');
179
+ });
180
+ });
181
+ });
182
+ //# sourceMappingURL=cliStatus.routes.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cliStatus.routes.test.js","sourceRoot":"","sources":["../../../../../../../../src/tests/units/modules/cli-configuration/interface-adapters/controllers/http/cliStatus.routes.test.ts"],"names":[],"mappings":"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"}
@@ -128,6 +128,136 @@ describe('projectConfigRoutes — GET /api/project-config', () => {
128
128
  expect(payload.error).toMatch(/review-back/);
129
129
  await app.close();
130
130
  });
131
+ it('returns 400 when the path query parameter is missing', async () => {
132
+ const app = await buildApp();
133
+ const response = await app.inject({
134
+ method: 'GET',
135
+ url: '/api/project-config',
136
+ });
137
+ expect(response.statusCode).toBe(400);
138
+ const payload = response.json();
139
+ expect(payload.success).toBe(false);
140
+ expect(payload.error).toBe('Project path required');
141
+ await app.close();
142
+ });
143
+ it('returns 400 when the path is not absolute', async () => {
144
+ const app = await buildApp();
145
+ const response = await app.inject({
146
+ method: 'GET',
147
+ url: '/api/project-config?path=relative/path',
148
+ });
149
+ expect(response.statusCode).toBe(400);
150
+ expect(response.json().error).toBe('Invalid path (must be absolute without ..)');
151
+ await app.close();
152
+ });
153
+ it('reports missing base required fields when they are absent', async () => {
154
+ vi.mocked(fsPromises.readFile).mockResolvedValue(jsonResponse({ reviewSkill: 'review-front' }));
155
+ const app = await buildApp();
156
+ const response = await app.inject({
157
+ method: 'GET',
158
+ url: '/api/project-config?path=/fake/project',
159
+ });
160
+ const payload = response.json();
161
+ expect(payload.success).toBe(false);
162
+ expect(payload.error).toMatch(/Missing fields:/);
163
+ expect(payload.error).toMatch(/github/);
164
+ await app.close();
165
+ });
166
+ it('resolves the reviewSkill SKILL.md when reviewSkill is explicitly set', async () => {
167
+ vi.mocked(fsPromises.readFile).mockResolvedValue(jsonResponse(ProjectConfigFactory.create({ reviewSkill: 'review-front' })));
168
+ const statMock = vi.mocked(fsPromises.stat);
169
+ statMock.mockResolvedValue(fakeStats());
170
+ const app = await buildApp();
171
+ const response = await app.inject({
172
+ method: 'GET',
173
+ url: '/api/project-config?path=/fake/project',
174
+ });
175
+ expect(response.json().success).toBe(true);
176
+ const checkedPaths = statMock.mock.calls.map((call) => String(call[0]));
177
+ expect(checkedPaths.some((checkedPath) => checkedPath.includes('review-front/SKILL.md'))).toBe(true);
178
+ await app.close();
179
+ });
180
+ it('rejects a config whose agents field is not an array', async () => {
181
+ const config = { ...ProjectConfigFactory.create(), agents: 'not-an-array' };
182
+ vi.mocked(fsPromises.readFile).mockResolvedValue(jsonResponse(config));
183
+ vi.mocked(fsPromises.stat).mockResolvedValue(fakeStats());
184
+ const app = await buildApp();
185
+ const response = await app.inject({
186
+ method: 'GET',
187
+ url: '/api/project-config?path=/fake/project',
188
+ });
189
+ const payload = response.json();
190
+ expect(payload.success).toBe(false);
191
+ expect(payload.error).toBe('Field "agents" must be an array');
192
+ await app.close();
193
+ });
194
+ it('rejects a config whose agents entries are malformed', async () => {
195
+ const config = {
196
+ ...ProjectConfigFactory.create(),
197
+ agents: [{ name: '', displayName: '' }],
198
+ };
199
+ vi.mocked(fsPromises.readFile).mockResolvedValue(jsonResponse(config));
200
+ vi.mocked(fsPromises.stat).mockResolvedValue(fakeStats());
201
+ const app = await buildApp();
202
+ const response = await app.inject({
203
+ method: 'GET',
204
+ url: '/api/project-config?path=/fake/project',
205
+ });
206
+ const payload = response.json();
207
+ expect(payload.success).toBe(false);
208
+ expect(payload.error).toMatch(/Invalid agents format/);
209
+ await app.close();
210
+ });
211
+ it('accepts a config with a well-formed agents array', async () => {
212
+ vi.mocked(fsPromises.readFile).mockResolvedValue(jsonResponse(ProjectConfigFactory.create({
213
+ agents: [{ name: 'security', displayName: 'Security Auditor' }],
214
+ })));
215
+ vi.mocked(fsPromises.stat).mockResolvedValue(fakeStats());
216
+ const app = await buildApp();
217
+ const response = await app.inject({
218
+ method: 'GET',
219
+ url: '/api/project-config?path=/fake/project',
220
+ });
221
+ expect(response.json().success).toBe(true);
222
+ await app.close();
223
+ });
224
+ it('returns the ENOENT message when config.json is missing', async () => {
225
+ const enoentError = Object.assign(new Error('no such file'), { code: 'ENOENT' });
226
+ vi.mocked(fsPromises.readFile).mockRejectedValue(enoentError);
227
+ const app = await buildApp();
228
+ const response = await app.inject({
229
+ method: 'GET',
230
+ url: '/api/project-config?path=/fake/project',
231
+ });
232
+ const payload = response.json();
233
+ expect(payload.success).toBe(false);
234
+ expect(payload.error).toBe('config.json file not found in .claude/reviews/');
235
+ await app.close();
236
+ });
237
+ it('returns a read error when the config file is not valid JSON', async () => {
238
+ vi.mocked(fsPromises.readFile).mockResolvedValue('not-json{');
239
+ const app = await buildApp();
240
+ const response = await app.inject({
241
+ method: 'GET',
242
+ url: '/api/project-config?path=/fake/project',
243
+ });
244
+ const payload = response.json();
245
+ expect(payload.success).toBe(false);
246
+ expect(payload.error).toMatch(/^Read error: /);
247
+ await app.close();
248
+ });
249
+ it('stringifies a non-Error thrown value in the read error message', async () => {
250
+ vi.mocked(fsPromises.readFile).mockRejectedValue('boom');
251
+ const app = await buildApp();
252
+ const response = await app.inject({
253
+ method: 'GET',
254
+ url: '/api/project-config?path=/fake/project',
255
+ });
256
+ const payload = response.json();
257
+ expect(payload.success).toBe(false);
258
+ expect(payload.error).toBe('Read error: boom');
259
+ await app.close();
260
+ });
131
261
  });
132
262
  function baseConfig(overrides = {}) {
133
263
  return {
@@ -367,5 +497,100 @@ describe('projectConfigRoutes — PATCH /api/project-config', () => {
367
497
  expect(response.json().config.maxConcurrentReviews).toBeUndefined();
368
498
  await app.close();
369
499
  });
500
+ it('returns 501 when the PATCH use case is not configured', async () => {
501
+ const app = Fastify();
502
+ await app.register(projectConfigRoutes);
503
+ const response = await app.inject({
504
+ method: 'PATCH',
505
+ url: '/api/project-config?path=' + encodeURIComponent('/repo/A'),
506
+ payload: { language: 'en' },
507
+ });
508
+ expect(response.statusCode).toBe(501);
509
+ expect(response.json().error).toBe('PATCH not configured');
510
+ await app.close();
511
+ });
512
+ it('returns 400 when the body is not a JSON object', async () => {
513
+ const gateway = new StubProjectConfigGateway();
514
+ gateway.set('/repo/A', baseConfig());
515
+ const app = await buildAppWithPatch(gateway);
516
+ const response = await app.inject({
517
+ method: 'PATCH',
518
+ url: '/api/project-config?path=' + encodeURIComponent('/repo/A'),
519
+ headers: { 'content-type': 'application/json' },
520
+ payload: JSON.stringify(['not', 'an', 'object']),
521
+ });
522
+ expect(response.statusCode).toBe(400);
523
+ expect(response.json().error).toBe('Body must be a JSON object');
524
+ await app.close();
525
+ });
526
+ it('applies a valid defaultModel from the patch body', async () => {
527
+ const gateway = new StubProjectConfigGateway();
528
+ gateway.set('/repo/A', baseConfig({ defaultModel: 'sonnet' }));
529
+ const app = await buildAppWithPatch(gateway);
530
+ const response = await app.inject({
531
+ method: 'PATCH',
532
+ url: '/api/project-config?path=' + encodeURIComponent('/repo/A'),
533
+ payload: { defaultModel: 'opus' },
534
+ });
535
+ expect(response.statusCode).toBe(200);
536
+ expect(response.json().config.defaultModel).toBe('opus');
537
+ await app.close();
538
+ });
539
+ it('treats an empty-string qualityThreshold as a clear request', async () => {
540
+ const gateway = new StubProjectConfigGateway();
541
+ gateway.set('/repo/A', baseConfig({ qualityThreshold: 7 }));
542
+ const app = await buildAppWithPatch(gateway);
543
+ const response = await app.inject({
544
+ method: 'PATCH',
545
+ url: '/api/project-config?path=' + encodeURIComponent('/repo/A'),
546
+ payload: { qualityThreshold: '' },
547
+ });
548
+ expect(response.statusCode).toBe(200);
549
+ expect(response.json().config.qualityThreshold).toBeUndefined();
550
+ await app.close();
551
+ });
552
+ it('keeps a boolean maxConcurrentReviews to surface a validation error', async () => {
553
+ const gateway = new StubProjectConfigGateway();
554
+ gateway.set('/repo/A', baseConfig());
555
+ const app = await buildAppWithPatch(gateway);
556
+ const response = await app.inject({
557
+ method: 'PATCH',
558
+ url: '/api/project-config?path=' + encodeURIComponent('/repo/A'),
559
+ payload: { maxConcurrentReviews: true },
560
+ });
561
+ expect(response.statusCode).toBe(400);
562
+ await app.close();
563
+ });
564
+ it('applies reviewSkill and reviewFollowupSkill string values from the patch', async () => {
565
+ const gateway = new StubProjectConfigGateway();
566
+ gateway.set('/repo/A', baseConfig());
567
+ const app = await buildAppWithPatch(gateway);
568
+ const response = await app.inject({
569
+ method: 'PATCH',
570
+ url: '/api/project-config?path=' + encodeURIComponent('/repo/A'),
571
+ payload: { reviewSkill: 'review-back', reviewFollowupSkill: 'review-followup-v2' },
572
+ });
573
+ expect(response.statusCode).toBe(200);
574
+ const body = response.json();
575
+ expect(body.config.reviewSkill).toBe('review-back');
576
+ expect(body.config.reviewFollowupSkill).toBe('review-followup-v2');
577
+ await app.close();
578
+ });
579
+ it('invokes the onSaved callback after a successful update', async () => {
580
+ const gateway = new StubProjectConfigGateway();
581
+ gateway.set('/repo/A', baseConfig());
582
+ const updateProjectConfig = new UpdateProjectConfigUseCase(gateway);
583
+ const onSaved = vi.fn();
584
+ const app = Fastify();
585
+ await app.register(projectConfigRoutes, { updateProjectConfig, onSaved });
586
+ const response = await app.inject({
587
+ method: 'PATCH',
588
+ url: '/api/project-config?path=' + encodeURIComponent('/repo/A'),
589
+ payload: { language: 'en' },
590
+ });
591
+ expect(response.statusCode).toBe(200);
592
+ expect(onSaved).toHaveBeenCalledWith('/repo/A');
593
+ await app.close();
594
+ });
370
595
  });
371
596
  //# sourceMappingURL=projectConfig.routes.test.js.map