@shepai/cli 1.70.1 → 1.71.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 (312) hide show
  1. package/dist/packages/core/src/application/ports/output/services/deployment-service.interface.d.ts +32 -0
  2. package/dist/packages/core/src/application/ports/output/services/deployment-service.interface.d.ts.map +1 -1
  3. package/dist/packages/core/src/infrastructure/services/deployment/deployment.service.d.ts +16 -2
  4. package/dist/packages/core/src/infrastructure/services/deployment/deployment.service.d.ts.map +1 -1
  5. package/dist/packages/core/src/infrastructure/services/deployment/deployment.service.js +46 -9
  6. package/dist/packages/core/src/infrastructure/services/deployment/log-ring-buffer.d.ts +23 -0
  7. package/dist/packages/core/src/infrastructure/services/deployment/log-ring-buffer.d.ts.map +1 -0
  8. package/dist/packages/core/src/infrastructure/services/deployment/log-ring-buffer.js +46 -0
  9. package/dist/src/presentation/web/app/actions/get-deployment-logs.d.ts +3 -0
  10. package/dist/src/presentation/web/app/actions/get-deployment-logs.d.ts.map +1 -0
  11. package/dist/src/presentation/web/app/actions/get-deployment-logs.js +9 -0
  12. package/dist/src/presentation/web/app/actions/get-feature-artifact.d.ts +2 -0
  13. package/dist/src/presentation/web/app/actions/get-feature-artifact.d.ts.map +1 -1
  14. package/dist/src/presentation/web/app/actions/get-feature-artifact.js +23 -1
  15. package/dist/src/presentation/web/app/api/deployment-logs/route.d.ts +15 -0
  16. package/dist/src/presentation/web/app/api/deployment-logs/route.d.ts.map +1 -0
  17. package/dist/src/presentation/web/app/api/deployment-logs/route.js +94 -0
  18. package/dist/src/presentation/web/components/common/base-drawer/base-drawer.js +2 -2
  19. package/dist/src/presentation/web/components/common/base-drawer/base-drawer.stories.js +1 -1
  20. package/dist/src/presentation/web/components/common/control-center-drawer/control-center-drawer.d.ts.map +1 -1
  21. package/dist/src/presentation/web/components/common/control-center-drawer/control-center-drawer.js +34 -4
  22. package/dist/src/presentation/web/components/common/deployment-status-badge/deployment-status-badge.d.ts +2 -1
  23. package/dist/src/presentation/web/components/common/deployment-status-badge/deployment-status-badge.d.ts.map +1 -1
  24. package/dist/src/presentation/web/components/common/deployment-status-badge/deployment-status-badge.js +16 -5
  25. package/dist/src/presentation/web/components/common/deployment-status-badge/deployment-status-badge.stories.d.ts +4 -0
  26. package/dist/src/presentation/web/components/common/deployment-status-badge/deployment-status-badge.stories.d.ts.map +1 -1
  27. package/dist/src/presentation/web/components/common/deployment-status-badge/deployment-status-badge.stories.js +8 -0
  28. package/dist/src/presentation/web/components/common/merge-review/merge-review.js +1 -1
  29. package/dist/src/presentation/web/components/common/prd-questionnaire/prd-questionnaire.js +1 -1
  30. package/dist/src/presentation/web/components/common/product-decisions-summary/index.d.ts +3 -0
  31. package/dist/src/presentation/web/components/common/product-decisions-summary/index.d.ts.map +1 -0
  32. package/dist/src/presentation/web/components/common/product-decisions-summary/index.js +1 -0
  33. package/dist/src/presentation/web/components/common/product-decisions-summary/product-decisions-summary-config.d.ts +23 -0
  34. package/dist/src/presentation/web/components/common/product-decisions-summary/product-decisions-summary-config.d.ts.map +1 -0
  35. package/dist/src/presentation/web/components/common/product-decisions-summary/product-decisions-summary-config.js +1 -0
  36. package/dist/src/presentation/web/components/common/product-decisions-summary/product-decisions-summary.d.ts +3 -0
  37. package/dist/src/presentation/web/components/common/product-decisions-summary/product-decisions-summary.d.ts.map +1 -0
  38. package/dist/src/presentation/web/components/common/product-decisions-summary/product-decisions-summary.js +13 -0
  39. package/dist/src/presentation/web/components/common/product-decisions-summary/product-decisions-summary.stories.d.ts +14 -0
  40. package/dist/src/presentation/web/components/common/product-decisions-summary/product-decisions-summary.stories.d.ts.map +1 -0
  41. package/dist/src/presentation/web/components/common/product-decisions-summary/product-decisions-summary.stories.js +74 -0
  42. package/dist/src/presentation/web/components/common/repository-node/repository-node.d.ts.map +1 -1
  43. package/dist/src/presentation/web/components/common/repository-node/repository-node.js +1 -1
  44. package/dist/src/presentation/web/components/common/server-log-viewer/index.d.ts +2 -0
  45. package/dist/src/presentation/web/components/common/server-log-viewer/index.d.ts.map +1 -0
  46. package/dist/src/presentation/web/components/common/server-log-viewer/index.js +1 -0
  47. package/dist/src/presentation/web/components/common/server-log-viewer/server-log-viewer.d.ts +15 -0
  48. package/dist/src/presentation/web/components/common/server-log-viewer/server-log-viewer.d.ts.map +1 -0
  49. package/dist/src/presentation/web/components/common/server-log-viewer/server-log-viewer.js +31 -0
  50. package/dist/src/presentation/web/components/common/server-log-viewer/server-log-viewer.stories.d.ts +18 -0
  51. package/dist/src/presentation/web/components/common/server-log-viewer/server-log-viewer.stories.d.ts.map +1 -0
  52. package/dist/src/presentation/web/components/common/server-log-viewer/server-log-viewer.stories.js +113 -0
  53. package/dist/src/presentation/web/components/common/tech-decisions-review/index.d.ts +1 -1
  54. package/dist/src/presentation/web/components/common/tech-decisions-review/index.d.ts.map +1 -1
  55. package/dist/src/presentation/web/components/common/tech-decisions-review/index.js +1 -1
  56. package/dist/src/presentation/web/components/common/tech-decisions-review/tech-decisions-review.d.ts +8 -1
  57. package/dist/src/presentation/web/components/common/tech-decisions-review/tech-decisions-review.d.ts.map +1 -1
  58. package/dist/src/presentation/web/components/common/tech-decisions-review/tech-decisions-review.js +11 -2
  59. package/dist/src/presentation/web/components/common/tech-review-tabs/index.d.ts +3 -0
  60. package/dist/src/presentation/web/components/common/tech-review-tabs/index.d.ts.map +1 -0
  61. package/dist/src/presentation/web/components/common/tech-review-tabs/index.js +1 -0
  62. package/dist/src/presentation/web/components/common/tech-review-tabs/tech-review-tabs-config.d.ts +17 -0
  63. package/dist/src/presentation/web/components/common/tech-review-tabs/tech-review-tabs-config.d.ts.map +1 -0
  64. package/dist/src/presentation/web/components/common/tech-review-tabs/tech-review-tabs-config.js +1 -0
  65. package/dist/src/presentation/web/components/common/tech-review-tabs/tech-review-tabs.d.ts +3 -0
  66. package/dist/src/presentation/web/components/common/tech-review-tabs/tech-review-tabs.d.ts.map +1 -0
  67. package/dist/src/presentation/web/components/common/tech-review-tabs/tech-review-tabs.js +12 -0
  68. package/dist/src/presentation/web/components/common/tech-review-tabs/tech-review-tabs.stories.d.ts +16 -0
  69. package/dist/src/presentation/web/components/common/tech-review-tabs/tech-review-tabs.stories.d.ts.map +1 -0
  70. package/dist/src/presentation/web/components/common/tech-review-tabs/tech-review-tabs.stories.js +110 -0
  71. package/dist/src/presentation/web/hooks/use-deployment-logs.d.ts +7 -0
  72. package/dist/src/presentation/web/hooks/use-deployment-logs.d.ts.map +1 -0
  73. package/dist/src/presentation/web/hooks/use-deployment-logs.js +50 -0
  74. package/dist/tsconfig.build.tsbuildinfo +1 -1
  75. package/package.json +1 -1
  76. package/web/.next/BUILD_ID +1 -1
  77. package/web/.next/app-path-routes-manifest.json +1 -0
  78. package/web/.next/build-manifest.json +2 -2
  79. package/web/.next/cache/.previewinfo +1 -1
  80. package/web/.next/cache/.rscinfo +1 -1
  81. package/web/.next/cache/.tsbuildinfo +1 -1
  82. package/web/.next/cache/config.json +3 -3
  83. package/web/.next/fallback-build-manifest.json +2 -2
  84. package/web/.next/prerender-manifest.json +3 -3
  85. package/web/.next/required-server-files.js +1 -1
  86. package/web/.next/required-server-files.json +1 -1
  87. package/web/.next/routes-manifest.json +6 -0
  88. package/web/.next/server/app/_global-error.html +2 -2
  89. package/web/.next/server/app/_global-error.rsc +1 -1
  90. package/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  91. package/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  92. package/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  93. package/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  94. package/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  95. package/web/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
  96. package/web/.next/server/app/_not-found/page.js.nft.json +1 -1
  97. package/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  98. package/web/.next/server/app/api/deployment-logs/route/app-paths-manifest.json +3 -0
  99. package/web/.next/server/app/api/deployment-logs/route/build-manifest.json +11 -0
  100. package/web/.next/server/app/api/deployment-logs/route/server-reference-manifest.json +4 -0
  101. package/web/.next/server/app/api/deployment-logs/route.js +6 -0
  102. package/web/.next/server/app/api/deployment-logs/route.js.map +5 -0
  103. package/web/.next/server/app/api/deployment-logs/route.js.nft.json +1 -0
  104. package/web/.next/server/app/api/deployment-logs/route_client-reference-manifest.js +2 -0
  105. package/web/.next/server/app/api/tools/[id]/install/route.js +1 -1
  106. package/web/.next/server/app/api/tools/[id]/install/route.js.nft.json +1 -1
  107. package/web/.next/server/app/page/server-reference-manifest.json +51 -36
  108. package/web/.next/server/app/page.js +1 -1
  109. package/web/.next/server/app/page.js.nft.json +1 -1
  110. package/web/.next/server/app/page_client-reference-manifest.js +1 -1
  111. package/web/.next/server/app/skills/page/server-reference-manifest.json +25 -10
  112. package/web/.next/server/app/skills/page.js +1 -1
  113. package/web/.next/server/app/skills/page.js.nft.json +1 -1
  114. package/web/.next/server/app/skills/page_client-reference-manifest.js +1 -1
  115. package/web/.next/server/app/tools/page/server-reference-manifest.json +1 -1
  116. package/web/.next/server/app/tools/page.js.nft.json +1 -1
  117. package/web/.next/server/app/tools/page_client-reference-manifest.js +1 -1
  118. package/web/.next/server/app/version/page/server-reference-manifest.json +1 -1
  119. package/web/.next/server/app/version/page.js +1 -1
  120. package/web/.next/server/app/version/page.js.nft.json +1 -1
  121. package/web/.next/server/app/version/page_client-reference-manifest.js +1 -1
  122. package/web/.next/server/app-paths-manifest.json +1 -0
  123. package/web/.next/server/chunks/744ca_web__next-internal_server_app_api_deployment-logs_route_actions_b785cd3a.js +3 -0
  124. package/web/.next/server/chunks/744ca_web__next-internal_server_app_api_deployment-logs_route_actions_b785cd3a.js.map +1 -0
  125. package/web/.next/server/chunks/[root-of-the-server]__9a136c79._.js +9 -0
  126. package/web/.next/server/chunks/[root-of-the-server]__9a136c79._.js.map +1 -0
  127. package/web/.next/server/chunks/{[root-of-the-server]__09413611._.js → [root-of-the-server]__e926de65._.js} +2 -2
  128. package/web/.next/server/chunks/{[root-of-the-server]__09413611._.js.map → [root-of-the-server]__e926de65._.js.map} +1 -1
  129. package/web/.next/server/chunks/ssr/[root-of-the-server]__08ba9bd3._.js +1 -1
  130. package/web/.next/server/chunks/ssr/[root-of-the-server]__08ba9bd3._.js.map +1 -1
  131. package/web/.next/server/chunks/ssr/[root-of-the-server]__5f968713._.js +3 -0
  132. package/web/.next/server/chunks/ssr/[root-of-the-server]__5f968713._.js.map +1 -0
  133. package/web/.next/server/chunks/ssr/[root-of-the-server]__6b17a22d._.js +1 -1
  134. package/web/.next/server/chunks/ssr/[root-of-the-server]__6b17a22d._.js.map +1 -1
  135. package/web/.next/server/chunks/ssr/{[root-of-the-server]__249c74f6._.js → [root-of-the-server]__6e8b5181._.js} +2 -2
  136. package/web/.next/server/chunks/ssr/[root-of-the-server]__804c006d._.js +1 -1
  137. package/web/.next/server/chunks/ssr/[root-of-the-server]__804c006d._.js.map +1 -1
  138. package/web/.next/server/chunks/ssr/[root-of-the-server]__970ba1be._.js +3 -0
  139. package/web/.next/server/chunks/ssr/[root-of-the-server]__970ba1be._.js.map +1 -0
  140. package/web/.next/server/chunks/ssr/[root-of-the-server]__97a1f9c2._.js +4 -0
  141. package/web/.next/server/chunks/ssr/[root-of-the-server]__97a1f9c2._.js.map +1 -0
  142. package/web/.next/server/chunks/ssr/[root-of-the-server]__9add7c3a._.js +2 -2
  143. package/web/.next/server/chunks/ssr/[root-of-the-server]__9add7c3a._.js.map +1 -1
  144. package/web/.next/server/chunks/ssr/[root-of-the-server]__ad7c18fa._.js +9 -0
  145. package/web/.next/server/chunks/ssr/[root-of-the-server]__ad7c18fa._.js.map +1 -0
  146. package/web/.next/server/chunks/ssr/[root-of-the-server]__b22d8535._.js +3 -0
  147. package/web/.next/server/chunks/ssr/[root-of-the-server]__b22d8535._.js.map +1 -0
  148. package/web/.next/server/chunks/ssr/_25e0eb34._.js +3 -0
  149. package/web/.next/server/chunks/ssr/_25e0eb34._.js.map +1 -0
  150. package/web/.next/server/chunks/ssr/_49bf495c._.js +1 -1
  151. package/web/.next/server/chunks/ssr/_49bf495c._.js.map +1 -1
  152. package/web/.next/server/chunks/ssr/_68b5e0de._.js +1 -1
  153. package/web/.next/server/chunks/ssr/_68b5e0de._.js.map +1 -1
  154. package/web/.next/server/chunks/ssr/_725584e5._.js +1 -1
  155. package/web/.next/server/chunks/ssr/_725584e5._.js.map +1 -1
  156. package/web/.next/server/chunks/ssr/_72ce07df._.js +3 -0
  157. package/web/.next/server/chunks/ssr/_72ce07df._.js.map +1 -0
  158. package/web/.next/server/chunks/ssr/{node_modules__pnpm_febcbea6._.js → node_modules__pnpm_3288606c._.js} +2 -2
  159. package/web/.next/server/chunks/ssr/{node_modules__pnpm_febcbea6._.js.map → node_modules__pnpm_3288606c._.js.map} +1 -1
  160. package/web/.next/server/chunks/ssr/src_presentation_web_components_5124369c._.js +3 -0
  161. package/web/.next/server/chunks/ssr/src_presentation_web_components_5124369c._.js.map +1 -0
  162. package/web/.next/server/chunks/ssr/src_presentation_web_components_ui_tabs_tsx_b226ea9b._.js +3 -0
  163. package/web/.next/server/chunks/ssr/src_presentation_web_components_ui_tabs_tsx_b226ea9b._.js.map +1 -0
  164. package/web/.next/server/pages/500.html +2 -2
  165. package/web/.next/server/server-reference-manifest.js +1 -1
  166. package/web/.next/server/server-reference-manifest.json +64 -42
  167. package/web/.next/standalone/src/presentation/web/.next/BUILD_ID +1 -1
  168. package/web/.next/standalone/src/presentation/web/.next/app-path-routes-manifest.json +1 -0
  169. package/web/.next/standalone/src/presentation/web/.next/build-manifest.json +2 -2
  170. package/web/.next/standalone/src/presentation/web/.next/prerender-manifest.json +3 -3
  171. package/web/.next/standalone/src/presentation/web/.next/required-server-files.json +1 -1
  172. package/web/.next/standalone/src/presentation/web/.next/routes-manifest.json +6 -0
  173. package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.html +2 -2
  174. package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.rsc +1 -1
  175. package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  176. package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  177. package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  178. package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  179. package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  180. package/web/.next/standalone/src/presentation/web/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
  181. package/web/.next/standalone/src/presentation/web/.next/server/app/_not-found/page.js.nft.json +1 -1
  182. package/web/.next/standalone/src/presentation/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  183. package/web/.next/standalone/src/presentation/web/.next/server/app/api/deployment-logs/route/app-paths-manifest.json +3 -0
  184. package/web/.next/standalone/src/presentation/web/.next/server/app/api/deployment-logs/route/build-manifest.json +11 -0
  185. package/web/.next/standalone/src/presentation/web/.next/server/app/api/deployment-logs/route/server-reference-manifest.json +4 -0
  186. package/web/.next/standalone/src/presentation/web/.next/server/app/api/deployment-logs/route.js +6 -0
  187. package/web/.next/standalone/src/presentation/web/.next/server/app/api/deployment-logs/route.js.map +5 -0
  188. package/web/.next/standalone/src/presentation/web/.next/server/app/api/deployment-logs/route.js.nft.json +1 -0
  189. package/web/.next/standalone/src/presentation/web/.next/server/app/api/deployment-logs/route_client-reference-manifest.js +2 -0
  190. package/web/.next/standalone/src/presentation/web/.next/server/app/api/tools/[id]/install/route.js +1 -1
  191. package/web/.next/standalone/src/presentation/web/.next/server/app/api/tools/[id]/install/route.js.nft.json +1 -1
  192. package/web/.next/standalone/src/presentation/web/.next/server/app/page/server-reference-manifest.json +51 -36
  193. package/web/.next/standalone/src/presentation/web/.next/server/app/page.js +1 -1
  194. package/web/.next/standalone/src/presentation/web/.next/server/app/page.js.nft.json +1 -1
  195. package/web/.next/standalone/src/presentation/web/.next/server/app/page_client-reference-manifest.js +1 -1
  196. package/web/.next/standalone/src/presentation/web/.next/server/app/skills/page/server-reference-manifest.json +25 -10
  197. package/web/.next/standalone/src/presentation/web/.next/server/app/skills/page.js +1 -1
  198. package/web/.next/standalone/src/presentation/web/.next/server/app/skills/page.js.nft.json +1 -1
  199. package/web/.next/standalone/src/presentation/web/.next/server/app/skills/page_client-reference-manifest.js +1 -1
  200. package/web/.next/standalone/src/presentation/web/.next/server/app/tools/page/server-reference-manifest.json +1 -1
  201. package/web/.next/standalone/src/presentation/web/.next/server/app/tools/page.js.nft.json +1 -1
  202. package/web/.next/standalone/src/presentation/web/.next/server/app/tools/page_client-reference-manifest.js +1 -1
  203. package/web/.next/standalone/src/presentation/web/.next/server/app/version/page/server-reference-manifest.json +1 -1
  204. package/web/.next/standalone/src/presentation/web/.next/server/app/version/page.js +1 -1
  205. package/web/.next/standalone/src/presentation/web/.next/server/app/version/page.js.nft.json +1 -1
  206. package/web/.next/standalone/src/presentation/web/.next/server/app/version/page_client-reference-manifest.js +1 -1
  207. package/web/.next/standalone/src/presentation/web/.next/server/app-paths-manifest.json +1 -0
  208. package/web/.next/standalone/src/presentation/web/.next/server/chunks/744ca_web__next-internal_server_app_api_deployment-logs_route_actions_b785cd3a.js +3 -0
  209. package/web/.next/standalone/src/presentation/web/.next/server/chunks/[root-of-the-server]__9a136c79._.js +9 -0
  210. package/web/.next/standalone/src/presentation/web/.next/server/chunks/{[root-of-the-server]__09413611._.js → [root-of-the-server]__e926de65._.js} +2 -2
  211. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__08ba9bd3._.js +1 -1
  212. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__5f968713._.js +3 -0
  213. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__6b17a22d._.js +1 -1
  214. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/{[root-of-the-server]__249c74f6._.js → [root-of-the-server]__6e8b5181._.js} +2 -2
  215. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__804c006d._.js +1 -1
  216. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__970ba1be._.js +3 -0
  217. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__97a1f9c2._.js +4 -0
  218. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__9add7c3a._.js +2 -2
  219. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__ad7c18fa._.js +9 -0
  220. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__b22d8535._.js +3 -0
  221. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/_25e0eb34._.js +3 -0
  222. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/_49bf495c._.js +1 -1
  223. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/_68b5e0de._.js +1 -1
  224. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/_725584e5._.js +1 -1
  225. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/_72ce07df._.js +3 -0
  226. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/{node_modules__pnpm_febcbea6._.js → node_modules__pnpm_3288606c._.js} +2 -2
  227. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/src_presentation_web_components_5124369c._.js +3 -0
  228. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/src_presentation_web_components_ui_tabs_tsx_b226ea9b._.js +3 -0
  229. package/web/.next/standalone/src/presentation/web/.next/server/pages/500.html +2 -2
  230. package/web/.next/standalone/src/presentation/web/.next/server/server-reference-manifest.js +1 -1
  231. package/web/.next/standalone/src/presentation/web/.next/server/server-reference-manifest.json +64 -42
  232. package/web/.next/standalone/src/presentation/web/app/actions/get-deployment-logs.ts +16 -0
  233. package/web/.next/standalone/src/presentation/web/app/actions/get-feature-artifact.ts +26 -1
  234. package/web/.next/standalone/src/presentation/web/app/api/deployment-logs/route.ts +112 -0
  235. package/web/.next/standalone/src/presentation/web/components/common/base-drawer/base-drawer.stories.tsx +1 -1
  236. package/web/.next/standalone/src/presentation/web/components/common/base-drawer/base-drawer.tsx +6 -2
  237. package/web/.next/standalone/src/presentation/web/components/common/control-center-drawer/control-center-drawer.tsx +44 -6
  238. package/web/.next/standalone/src/presentation/web/components/common/deployment-status-badge/deployment-status-badge.stories.tsx +10 -0
  239. package/web/.next/standalone/src/presentation/web/components/common/deployment-status-badge/deployment-status-badge.tsx +76 -23
  240. package/web/.next/standalone/src/presentation/web/components/common/merge-review/merge-review.tsx +1 -1
  241. package/web/.next/standalone/src/presentation/web/components/common/prd-questionnaire/prd-questionnaire.tsx +1 -1
  242. package/web/.next/standalone/src/presentation/web/components/common/product-decisions-summary/index.ts +6 -0
  243. package/web/.next/standalone/src/presentation/web/components/common/product-decisions-summary/product-decisions-summary-config.ts +24 -0
  244. package/web/.next/standalone/src/presentation/web/components/common/product-decisions-summary/product-decisions-summary.stories.tsx +87 -0
  245. package/web/.next/standalone/src/presentation/web/components/common/product-decisions-summary/product-decisions-summary.tsx +52 -0
  246. package/web/.next/standalone/src/presentation/web/components/common/repository-node/repository-node.tsx +5 -1
  247. package/web/.next/standalone/src/presentation/web/components/common/server-log-viewer/index.ts +6 -0
  248. package/web/.next/standalone/src/presentation/web/components/common/server-log-viewer/server-log-viewer.stories.tsx +168 -0
  249. package/web/.next/standalone/src/presentation/web/components/common/server-log-viewer/server-log-viewer.tsx +110 -0
  250. package/web/.next/standalone/src/presentation/web/components/common/tech-decisions-review/index.ts +1 -1
  251. package/web/.next/standalone/src/presentation/web/components/common/tech-decisions-review/tech-decisions-review.tsx +47 -29
  252. package/web/.next/standalone/src/presentation/web/components/common/tech-review-tabs/index.ts +2 -0
  253. package/web/.next/standalone/src/presentation/web/components/common/tech-review-tabs/tech-review-tabs-config.ts +17 -0
  254. package/web/.next/standalone/src/presentation/web/components/common/tech-review-tabs/tech-review-tabs.stories.tsx +129 -0
  255. package/web/.next/standalone/src/presentation/web/components/common/tech-review-tabs/tech-review-tabs.tsx +60 -0
  256. package/web/.next/standalone/src/presentation/web/hooks/use-deployment-logs.ts +67 -0
  257. package/web/.next/standalone/src/presentation/web/server.js +1 -1
  258. package/web/.next/static/chunks/01ae2241bd4b44b6.js +1 -0
  259. package/web/.next/static/chunks/20f7876f292cfd82.js +1 -0
  260. package/web/.next/static/chunks/224ed5f5dbd33154.css +2 -0
  261. package/web/.next/static/chunks/{87421ab1062a39b7.js → 3e1227e02ef8bcc6.js} +2 -2
  262. package/web/.next/static/chunks/{1ed0df845a1625f6.js → 62c8ebbd9c7bb287.js} +1 -1
  263. package/web/.next/static/chunks/673e0c1000a91948.js +1 -0
  264. package/web/.next/static/chunks/8b6df4f8e194d0ce.js +1 -0
  265. package/web/.next/static/chunks/b025563d959150a1.js +1 -0
  266. package/web/.next/static/chunks/{9db5c06001f3e664.js → c7fb052190a02a9c.js} +2 -2
  267. package/web/.next/static/chunks/d450d8db6be36b8d.js +1 -0
  268. package/web/.next/static/chunks/d9e3cf214ac2f386.js +1 -0
  269. package/web/.next/trace +1 -1
  270. package/web/.next/trace-build +1 -1
  271. package/web/.next/types/link.d.ts +1 -0
  272. package/web/.next/types/routes.d.ts +2 -1
  273. package/web/.next/types/validator.ts +9 -0
  274. package/web/.next/server/chunks/ssr/6769f_@radix-ui_react-roving-focus_dist_index_mjs_b3be3d8e._.js +0 -3
  275. package/web/.next/server/chunks/ssr/6769f_@radix-ui_react-roving-focus_dist_index_mjs_b3be3d8e._.js.map +0 -1
  276. package/web/.next/server/chunks/ssr/[root-of-the-server]__2ffb27f1._.js +0 -3
  277. package/web/.next/server/chunks/ssr/[root-of-the-server]__2ffb27f1._.js.map +0 -1
  278. package/web/.next/server/chunks/ssr/[root-of-the-server]__551fb7e1._.js +0 -4
  279. package/web/.next/server/chunks/ssr/[root-of-the-server]__551fb7e1._.js.map +0 -1
  280. package/web/.next/server/chunks/ssr/[root-of-the-server]__7f4180a1._.js +0 -3
  281. package/web/.next/server/chunks/ssr/[root-of-the-server]__7f4180a1._.js.map +0 -1
  282. package/web/.next/server/chunks/ssr/[root-of-the-server]__e41b5eec._.js +0 -9
  283. package/web/.next/server/chunks/ssr/[root-of-the-server]__e41b5eec._.js.map +0 -1
  284. package/web/.next/server/chunks/ssr/[root-of-the-server]__eaf6100f._.js +0 -3
  285. package/web/.next/server/chunks/ssr/[root-of-the-server]__eaf6100f._.js.map +0 -1
  286. package/web/.next/server/chunks/ssr/_28993370._.js +0 -3
  287. package/web/.next/server/chunks/ssr/_28993370._.js.map +0 -1
  288. package/web/.next/server/chunks/ssr/_690ea95f._.js +0 -3
  289. package/web/.next/server/chunks/ssr/_690ea95f._.js.map +0 -1
  290. package/web/.next/server/chunks/ssr/src_presentation_web_components_0286756b._.js +0 -3
  291. package/web/.next/server/chunks/ssr/src_presentation_web_components_0286756b._.js.map +0 -1
  292. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/6769f_@radix-ui_react-roving-focus_dist_index_mjs_b3be3d8e._.js +0 -3
  293. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__2ffb27f1._.js +0 -3
  294. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__551fb7e1._.js +0 -4
  295. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__7f4180a1._.js +0 -3
  296. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__e41b5eec._.js +0 -9
  297. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__eaf6100f._.js +0 -3
  298. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/_28993370._.js +0 -3
  299. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/_690ea95f._.js +0 -3
  300. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/src_presentation_web_components_0286756b._.js +0 -3
  301. package/web/.next/static/chunks/16e380b8dd9d11bc.js +0 -1
  302. package/web/.next/static/chunks/24e1d97cab8bad6d.js +0 -1
  303. package/web/.next/static/chunks/505c6a9e4c5d0808.js +0 -1
  304. package/web/.next/static/chunks/a186bbb822ccb655.css +0 -2
  305. package/web/.next/static/chunks/a5c59952485e875e.js +0 -1
  306. package/web/.next/static/chunks/a9626385607910b3.js +0 -1
  307. package/web/.next/static/chunks/d4379644a6145352.js +0 -1
  308. package/web/.next/static/chunks/fb703cf73aba2eb8.js +0 -1
  309. /package/web/.next/server/chunks/ssr/{[root-of-the-server]__249c74f6._.js.map → [root-of-the-server]__6e8b5181._.js.map} +0 -0
  310. /package/web/.next/static/{8xZoRkWykApYNlvamCGUe → F3mdpVVEgR5m2xHY_SnYx}/_buildManifest.js +0 -0
  311. /package/web/.next/static/{8xZoRkWykApYNlvamCGUe → F3mdpVVEgR5m2xHY_SnYx}/_clientMiddlewareManifest.json +0 -0
  312. /package/web/.next/static/{8xZoRkWykApYNlvamCGUe → F3mdpVVEgR5m2xHY_SnYx}/_ssgManifest.js +0 -0
@@ -10,6 +10,17 @@
10
10
  * - Infrastructure layer provides concrete implementations
11
11
  */
12
12
  import type { DeploymentState } from '../../../../domain/generated/output.js';
13
+ /** A single log line captured from a deployment's stdout or stderr. */
14
+ export interface LogEntry {
15
+ /** Which deployment produced this line. */
16
+ targetId: string;
17
+ /** Which output stream produced this line. */
18
+ stream: 'stdout' | 'stderr';
19
+ /** The line content (without trailing newline). */
20
+ line: string;
21
+ /** Timestamp (ms since epoch) when the line was captured. */
22
+ timestamp: number;
23
+ }
13
24
  /** Status snapshot returned by getStatus(). */
14
25
  export interface DeploymentStatus {
15
26
  /** Current lifecycle state of the deployment. */
@@ -58,5 +69,26 @@ export interface IDeploymentService {
58
69
  * Called during daemon shutdown to prevent orphaned dev server processes.
59
70
  */
60
71
  stopAll(): void;
72
+ /**
73
+ * Get the accumulated log buffer for a deployment.
74
+ *
75
+ * @param targetId - Unique identifier for the deployment target
76
+ * @returns Array of log entries in chronological order, or null if no deployment exists
77
+ */
78
+ getLogs(targetId: string): LogEntry[] | null;
79
+ /**
80
+ * Subscribe to real-time log events from all deployments.
81
+ *
82
+ * @param event - Event name (only 'log' is supported)
83
+ * @param handler - Callback invoked with each new log entry
84
+ */
85
+ on(event: 'log', handler: (entry: LogEntry) => void): void;
86
+ /**
87
+ * Unsubscribe from real-time log events.
88
+ *
89
+ * @param event - Event name (only 'log' is supported)
90
+ * @param handler - The same callback reference passed to on()
91
+ */
92
+ off(event: 'log', handler: (entry: LogEntry) => void): void;
61
93
  }
62
94
  //# sourceMappingURL=deployment-service.interface.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"deployment-service.interface.d.ts","sourceRoot":"","sources":["../../../../../../../../packages/core/src/application/ports/output/services/deployment-service.interface.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAEpE,+CAA+C;AAC/C,MAAM,WAAW,gBAAgB;IAC/B,iDAAiD;IACjD,KAAK,EAAE,eAAe,CAAC;IACvB,qEAAqE;IACrE,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;CACpB;AAED;;;;;;;;;GASG;AACH,MAAM,WAAW,kBAAkB;IACjC;;;;;;;;OAQG;IACH,KAAK,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAElD;;;;;;OAMG;IACH,IAAI,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEtC;;;;;OAKG;IACH,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,gBAAgB,GAAG,IAAI,CAAC;IAErD;;;OAGG;IACH,OAAO,IAAI,IAAI,CAAC;CACjB"}
1
+ {"version":3,"file":"deployment-service.interface.d.ts","sourceRoot":"","sources":["../../../../../../../../packages/core/src/application/ports/output/services/deployment-service.interface.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAEpE,uEAAuE;AACvE,MAAM,WAAW,QAAQ;IACvB,2CAA2C;IAC3C,QAAQ,EAAE,MAAM,CAAC;IACjB,8CAA8C;IAC9C,MAAM,EAAE,QAAQ,GAAG,QAAQ,CAAC;IAC5B,mDAAmD;IACnD,IAAI,EAAE,MAAM,CAAC;IACb,6DAA6D;IAC7D,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,+CAA+C;AAC/C,MAAM,WAAW,gBAAgB;IAC/B,iDAAiD;IACjD,KAAK,EAAE,eAAe,CAAC;IACvB,qEAAqE;IACrE,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;CACpB;AAED;;;;;;;;;GASG;AACH,MAAM,WAAW,kBAAkB;IACjC;;;;;;;;OAQG;IACH,KAAK,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAElD;;;;;;OAMG;IACH,IAAI,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEtC;;;;;OAKG;IACH,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,gBAAgB,GAAG,IAAI,CAAC;IAErD;;;OAGG;IACH,OAAO,IAAI,IAAI,CAAC;IAIhB;;;;;OAKG;IACH,OAAO,CAAC,QAAQ,EAAE,MAAM,GAAG,QAAQ,EAAE,GAAG,IAAI,CAAC;IAE7C;;;;;OAKG;IACH,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,KAAK,EAAE,QAAQ,KAAK,IAAI,GAAG,IAAI,CAAC;IAE3D;;;;;OAKG;IACH,GAAG,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,KAAK,EAAE,QAAQ,KAAK,IAAI,GAAG,IAAI,CAAC;CAC7D"}
@@ -7,7 +7,7 @@
7
7
  * shutdown (SIGTERM → poll → SIGKILL).
8
8
  */
9
9
  import { spawn } from 'node:child_process';
10
- import type { IDeploymentService, DeploymentStatus } from '../../../application/ports/output/services/deployment-service.interface.js';
10
+ import type { IDeploymentService, DeploymentStatus, LogEntry } from '../../../application/ports/output/services/deployment-service.interface.js';
11
11
  import { detectDevScript } from './detect-dev-script.js';
12
12
  export interface DeploymentServiceDeps {
13
13
  spawn: typeof spawn;
@@ -18,6 +18,7 @@ export interface DeploymentServiceDeps {
18
18
  export declare class DeploymentService implements IDeploymentService {
19
19
  private readonly deployments;
20
20
  private readonly deps;
21
+ private readonly emitter;
21
22
  constructor(deps?: Partial<DeploymentServiceDeps>);
22
23
  /**
23
24
  * Start a deployment for the given target.
@@ -37,12 +38,25 @@ export declare class DeploymentService implements IDeploymentService {
37
38
  * Force-stop all tracked deployments immediately (for daemon shutdown).
38
39
  */
39
40
  stopAll(): void;
41
+ /**
42
+ * Get the accumulated log buffer for a deployment.
43
+ */
44
+ getLogs(targetId: string): LogEntry[] | null;
45
+ /**
46
+ * Subscribe to real-time log events.
47
+ */
48
+ on(event: 'log', handler: (entry: LogEntry) => void): void;
49
+ /**
50
+ * Unsubscribe from real-time log events.
51
+ */
52
+ off(event: 'log', handler: (entry: LogEntry) => void): void;
40
53
  /**
41
54
  * Send SIGKILL to a process group.
42
55
  */
43
56
  private killProcess;
44
57
  /**
45
- * Attach a line-buffered listener on stdout or stderr that calls parsePort.
58
+ * Attach a line-buffered listener on stdout or stderr that calls parsePort
59
+ * and accumulates log entries.
46
60
  */
47
61
  private attachOutputListener;
48
62
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"deployment.service.d.ts","sourceRoot":"","sources":["../../../../../../../packages/core/src/infrastructure/services/deployment/deployment.service.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,KAAK,EAAqB,MAAM,oBAAoB,CAAC;AAE9D,OAAO,KAAK,EACV,kBAAkB,EAClB,gBAAgB,EACjB,MAAM,qEAAqE,CAAC;AAC7E,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAkBzD,MAAM,WAAW,qBAAqB;IACpC,KAAK,EAAE,OAAO,KAAK,CAAC;IACpB,eAAe,EAAE,OAAO,eAAe,CAAC;IACxC,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,OAAO,GAAG,MAAM,KAAK,IAAI,CAAC;IAC7D,OAAO,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC;CACnC;AAgBD,qBAAa,iBAAkB,YAAW,kBAAkB;IAC1D,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAsC;IAClE,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAwB;gBAEjC,IAAI,GAAE,OAAO,CAAC,qBAAqB,CAAM;IAIrD;;;OAGG;IACH,KAAK,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI;IA8EjD;;;OAGG;IACH,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,gBAAgB,GAAG,IAAI;IAYpD;;OAEG;IACG,IAAI,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAuC3C;;OAEG;IACH,OAAO,IAAI,IAAI;IAMf;;OAEG;IACH,OAAO,CAAC,WAAW;IAQnB;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAoD5B;;OAEG;YACW,aAAa;IAW3B;;OAEG;IACH,OAAO,CAAC,WAAW;CASpB"}
1
+ {"version":3,"file":"deployment.service.d.ts","sourceRoot":"","sources":["../../../../../../../packages/core/src/infrastructure/services/deployment/deployment.service.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,KAAK,EAAqB,MAAM,oBAAoB,CAAC;AAG9D,OAAO,KAAK,EACV,kBAAkB,EAClB,gBAAgB,EAChB,QAAQ,EACT,MAAM,qEAAqE,CAAC;AAC7E,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAoBzD,MAAM,WAAW,qBAAqB;IACpC,KAAK,EAAE,OAAO,KAAK,CAAC;IACpB,eAAe,EAAE,OAAO,eAAe,CAAC;IACxC,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,OAAO,GAAG,MAAM,KAAK,IAAI,CAAC;IAC7D,OAAO,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC;CACnC;AAgBD,qBAAa,iBAAkB,YAAW,kBAAkB;IAC1D,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAsC;IAClE,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAwB;IAC7C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAsB;gBAElC,IAAI,GAAE,OAAO,CAAC,qBAAqB,CAAM;IAIrD;;;OAGG;IACH,KAAK,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI;IA+EjD;;;OAGG;IACH,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,gBAAgB,GAAG,IAAI;IAYpD;;OAEG;IACG,IAAI,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAyC3C;;OAEG;IACH,OAAO,IAAI,IAAI;IAOf;;OAEG;IACH,OAAO,CAAC,QAAQ,EAAE,MAAM,GAAG,QAAQ,EAAE,GAAG,IAAI;IAM5C;;OAEG;IACH,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,KAAK,EAAE,QAAQ,KAAK,IAAI,GAAG,IAAI;IAI1D;;OAEG;IACH,GAAG,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,KAAK,EAAE,QAAQ,KAAK,IAAI,GAAG,IAAI;IAI3D;;OAEG;IACH,OAAO,CAAC,WAAW;IAQnB;;;OAGG;IACH,OAAO,CAAC,oBAAoB;IA+D5B;;OAEG;YACW,aAAa;IAW3B;;OAEG;IACH,OAAO,CAAC,WAAW;CASpB"}
@@ -7,10 +7,12 @@
7
7
  * shutdown (SIGTERM → poll → SIGKILL).
8
8
  */
9
9
  import { spawn } from 'node:child_process';
10
+ import { EventEmitter } from 'node:events';
10
11
  import { DeploymentState } from '../../../domain/generated/output.js';
11
12
  import { detectDevScript } from './detect-dev-script.js';
12
13
  import { createDeploymentLogger } from './deployment-logger.js';
13
14
  import { parsePort } from './parse-port.js';
15
+ import { LogRingBuffer } from './log-ring-buffer.js';
14
16
  const log = createDeploymentLogger('[DeploymentService]');
15
17
  const POLL_INTERVAL_MS = 200;
16
18
  const MAX_WAIT_MS = 5000;
@@ -31,6 +33,7 @@ const defaultDeps = {
31
33
  export class DeploymentService {
32
34
  deployments = new Map();
33
35
  deps;
36
+ emitter = new EventEmitter();
34
37
  constructor(deps = {}) {
35
38
  this.deps = { ...defaultDeps, ...deps };
36
39
  }
@@ -76,6 +79,7 @@ export class DeploymentService {
76
79
  targetId,
77
80
  stdoutBuffer: '',
78
81
  stderrBuffer: '',
82
+ logs: new LogRingBuffer(),
79
83
  };
80
84
  this.deployments.set(targetId, entry);
81
85
  // Attach stdout/stderr listeners for port detection
@@ -120,6 +124,7 @@ export class DeploymentService {
120
124
  return;
121
125
  }
122
126
  log.info(`stop("${targetId}") — sending SIGTERM to process group (pid=${entry.pid})`);
127
+ entry.logs.clear();
123
128
  // Send SIGTERM to process group
124
129
  try {
125
130
  this.deps.kill(-entry.pid, 'SIGTERM');
@@ -152,9 +157,31 @@ export class DeploymentService {
152
157
  */
153
158
  stopAll() {
154
159
  for (const entry of this.deployments.values()) {
160
+ entry.logs.clear();
155
161
  this.killProcess(entry);
156
162
  }
157
163
  }
164
+ /**
165
+ * Get the accumulated log buffer for a deployment.
166
+ */
167
+ getLogs(targetId) {
168
+ const entry = this.deployments.get(targetId);
169
+ if (!entry)
170
+ return null;
171
+ return entry.logs.getAll();
172
+ }
173
+ /**
174
+ * Subscribe to real-time log events.
175
+ */
176
+ on(event, handler) {
177
+ this.emitter.on(event, handler);
178
+ }
179
+ /**
180
+ * Unsubscribe from real-time log events.
181
+ */
182
+ off(event, handler) {
183
+ this.emitter.off(event, handler);
184
+ }
158
185
  /**
159
186
  * Send SIGKILL to a process group.
160
187
  */
@@ -167,7 +194,8 @@ export class DeploymentService {
167
194
  }
168
195
  }
169
196
  /**
170
- * Attach a line-buffered listener on stdout or stderr that calls parsePort.
197
+ * Attach a line-buffered listener on stdout or stderr that calls parsePort
198
+ * and accumulates log entries.
171
199
  */
172
200
  attachOutputListener(entry, stream) {
173
201
  const bufferKey = stream === 'stdout' ? 'stdoutBuffer' : 'stderrBuffer';
@@ -188,14 +216,23 @@ export class DeploymentService {
188
216
  if (!line.trim())
189
217
  continue;
190
218
  log.debug(`[${entry.targetId}] ${stream}: ${line}`);
191
- if (entry.state !== DeploymentState.Booting)
192
- break;
193
- const url = parsePort(line);
194
- if (url) {
195
- log.info(`[${entry.targetId}] Port detected — url="${url}" (from ${stream})`);
196
- entry.state = DeploymentState.Ready;
197
- entry.url = url;
198
- break;
219
+ // Accumulate in log buffer and emit event
220
+ const logEntry = {
221
+ targetId: entry.targetId,
222
+ stream,
223
+ line,
224
+ timestamp: Date.now(),
225
+ };
226
+ entry.logs.push(logEntry);
227
+ this.emitter.emit('log', logEntry);
228
+ // Port detection (only while Booting)
229
+ if (entry.state === DeploymentState.Booting) {
230
+ const url = parsePort(line);
231
+ if (url) {
232
+ log.info(`[${entry.targetId}] Port detected — url="${url}" (from ${stream})`);
233
+ entry.state = DeploymentState.Ready;
234
+ entry.url = url;
235
+ }
199
236
  }
200
237
  }
201
238
  });
@@ -0,0 +1,23 @@
1
+ /**
2
+ * LogRingBuffer — Fixed-capacity circular buffer for deployment log entries.
3
+ *
4
+ * Provides O(1) append with automatic eviction of the oldest entry when full.
5
+ * getAll() returns entries in chronological (insertion) order.
6
+ */
7
+ import type { LogEntry } from '../../../application/ports/output/services/deployment-service.interface.js';
8
+ export declare class LogRingBuffer {
9
+ readonly capacity: number;
10
+ private buffer;
11
+ private writeIndex;
12
+ private count;
13
+ constructor(capacity?: number);
14
+ /** Append a log entry. Evicts the oldest entry when at capacity. */
15
+ push(entry: LogEntry): void;
16
+ /** Return all entries in chronological order. */
17
+ getAll(): LogEntry[];
18
+ /** Remove all entries and reset the buffer. */
19
+ clear(): void;
20
+ /** Current number of entries in the buffer. */
21
+ get size(): number;
22
+ }
23
+ //# sourceMappingURL=log-ring-buffer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"log-ring-buffer.d.ts","sourceRoot":"","sources":["../../../../../../../packages/core/src/infrastructure/services/deployment/log-ring-buffer.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qEAAqE,CAAC;AAIpG,qBAAa,aAAa;IACxB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,OAAO,CAAC,MAAM,CAAa;IAC3B,OAAO,CAAC,UAAU,CAAK;IACvB,OAAO,CAAC,KAAK,CAAK;gBAEN,QAAQ,GAAE,MAAyB;IAK/C,oEAAoE;IACpE,IAAI,CAAC,KAAK,EAAE,QAAQ,GAAG,IAAI;IAU3B,iDAAiD;IACjD,MAAM,IAAI,QAAQ,EAAE;IAQpB,+CAA+C;IAC/C,KAAK,IAAI,IAAI;IAMb,+CAA+C;IAC/C,IAAI,IAAI,IAAI,MAAM,CAEjB;CACF"}
@@ -0,0 +1,46 @@
1
+ /**
2
+ * LogRingBuffer — Fixed-capacity circular buffer for deployment log entries.
3
+ *
4
+ * Provides O(1) append with automatic eviction of the oldest entry when full.
5
+ * getAll() returns entries in chronological (insertion) order.
6
+ */
7
+ const DEFAULT_CAPACITY = 5000;
8
+ export class LogRingBuffer {
9
+ capacity;
10
+ buffer;
11
+ writeIndex = 0;
12
+ count = 0;
13
+ constructor(capacity = DEFAULT_CAPACITY) {
14
+ this.capacity = capacity;
15
+ this.buffer = [];
16
+ }
17
+ /** Append a log entry. Evicts the oldest entry when at capacity. */
18
+ push(entry) {
19
+ if (this.count < this.capacity) {
20
+ this.buffer.push(entry);
21
+ this.count++;
22
+ }
23
+ else {
24
+ this.buffer[this.writeIndex] = entry;
25
+ }
26
+ this.writeIndex = (this.writeIndex + 1) % this.capacity;
27
+ }
28
+ /** Return all entries in chronological order. */
29
+ getAll() {
30
+ if (this.count < this.capacity) {
31
+ return this.buffer.slice();
32
+ }
33
+ // Buffer is full and may have wrapped — writeIndex points to the oldest entry
34
+ return [...this.buffer.slice(this.writeIndex), ...this.buffer.slice(0, this.writeIndex)];
35
+ }
36
+ /** Remove all entries and reset the buffer. */
37
+ clear() {
38
+ this.buffer = [];
39
+ this.writeIndex = 0;
40
+ this.count = 0;
41
+ }
42
+ /** Current number of entries in the buffer. */
43
+ get size() {
44
+ return this.count;
45
+ }
46
+ }
@@ -0,0 +1,3 @@
1
+ import type { LogEntry } from '../../../../../packages/core/src/application/ports/output/services/deployment-service.interface.js';
2
+ export declare function getDeploymentLogs(targetId: string): Promise<LogEntry[] | null>;
3
+ //# sourceMappingURL=get-deployment-logs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"get-deployment-logs.d.ts","sourceRoot":"","sources":["../../../../../../src/presentation/web/app/actions/get-deployment-logs.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAEV,QAAQ,EACT,MAAM,6EAA6E,CAAC;AAErF,wBAAsB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,EAAE,GAAG,IAAI,CAAC,CAOpF"}
@@ -0,0 +1,9 @@
1
+ 'use server';
2
+ import { resolve } from '../../lib/server-container.js';
3
+ export async function getDeploymentLogs(targetId) {
4
+ if (!targetId?.trim()) {
5
+ return null;
6
+ }
7
+ const deploymentService = resolve('IDeploymentService');
8
+ return deploymentService.getLogs(targetId);
9
+ }
@@ -1,7 +1,9 @@
1
1
  import type { FeatureArtifact } from '../../../../../packages/core/src/domain/generated/output.js';
2
2
  import type { PrdQuestionnaireData } from '../../../../../packages/core/src/domain/generated/output.js';
3
+ import type { ProductDecisionsSummaryData } from '../../components/common/product-decisions-summary/index.js';
3
4
  interface GetFeatureArtifactResult {
4
5
  questionnaire?: PrdQuestionnaireData;
6
+ productDecisions?: ProductDecisionsSummaryData;
5
7
  artifact?: FeatureArtifact;
6
8
  error?: string;
7
9
  }
@@ -1 +1 @@
1
- {"version":3,"file":"get-feature-artifact.d.ts","sourceRoot":"","sources":["../../../../../../src/presentation/web/app/actions/get-feature-artifact.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sCAAsC,CAAC;AAC5E,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,sCAAsC,CAAC;AAEjF,UAAU,wBAAwB;IAChC,aAAa,CAAC,EAAE,oBAAoB,CAAC;IACrC,QAAQ,CAAC,EAAE,eAAe,CAAC;IAC3B,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AA6BD,wBAAsB,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,wBAAwB,CAAC,CAc7F"}
1
+ {"version":3,"file":"get-feature-artifact.d.ts","sourceRoot":"","sources":["../../../../../../src/presentation/web/app/actions/get-feature-artifact.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sCAAsC,CAAC;AAC5E,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,sCAAsC,CAAC;AACjF,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,+CAA+C,CAAC;AAEjG,UAAU,wBAAwB;IAChC,aAAa,CAAC,EAAE,oBAAoB,CAAC;IACrC,gBAAgB,CAAC,EAAE,2BAA2B,CAAC;IAC/C,QAAQ,CAAC,EAAE,eAAe,CAAC;IAC3B,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAmDD,wBAAsB,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,wBAAwB,CAAC,CAe7F"}
@@ -26,6 +26,27 @@ function toQuestionnaireData(artifact) {
26
26
  },
27
27
  };
28
28
  }
29
+ /**
30
+ * Map FeatureArtifact openQuestions into a read-only summary for the
31
+ * Product Decisions tab in the tech review drawer.
32
+ */
33
+ function toProductDecisionsData(artifact) {
34
+ return {
35
+ question: 'Goal',
36
+ context: artifact.oneLiner,
37
+ questions: artifact.openQuestions
38
+ .filter((oq) => oq.resolved)
39
+ .map((oq) => {
40
+ const selected = oq.options?.find((o) => o.selected);
41
+ return {
42
+ question: oq.question,
43
+ selectedOption: selected?.option ?? oq.answer ?? 'N/A',
44
+ rationale: oq.selectionRationale ?? selected?.description ?? '',
45
+ wasRecommended: false,
46
+ };
47
+ }),
48
+ };
49
+ }
29
50
  export async function getFeatureArtifact(featureId) {
30
51
  if (!featureId.trim()) {
31
52
  return { error: 'Feature id is required' };
@@ -34,7 +55,8 @@ export async function getFeatureArtifact(featureId) {
34
55
  const useCase = resolve('GetFeatureArtifactUseCase');
35
56
  const artifact = await useCase.execute(featureId);
36
57
  const questionnaire = toQuestionnaireData(artifact);
37
- return { questionnaire, artifact };
58
+ const productDecisions = toProductDecisionsData(artifact);
59
+ return { questionnaire, productDecisions, artifact };
38
60
  }
39
61
  catch (error) {
40
62
  const message = error instanceof Error ? error.message : 'Failed to load feature artifact';
@@ -0,0 +1,15 @@
1
+ /**
2
+ * SSE API Route: GET /api/deployment-logs
3
+ *
4
+ * Streams deployment log events to the client via Server-Sent Events.
5
+ * Subscribes to the DeploymentService EventEmitter for real-time log
6
+ * entries, filtered by targetId.
7
+ *
8
+ * - Accepts ?targetId query parameter (required)
9
+ * - Sends log entries as SSE "log" events
10
+ * - Sends heartbeat comments every 30 seconds to keep connection alive
11
+ * - Cleans up EventEmitter subscription on client disconnect
12
+ */
13
+ export declare const dynamic = "force-dynamic";
14
+ export declare function GET(request: Request): Response;
15
+ //# sourceMappingURL=route.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"route.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/app/api/deployment-logs/route.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AASH,eAAO,MAAM,OAAO,kBAAkB,CAAC;AAIvC,wBAAgB,GAAG,CAAC,OAAO,EAAE,OAAO,GAAG,QAAQ,CAuF9C"}
@@ -0,0 +1,94 @@
1
+ /**
2
+ * SSE API Route: GET /api/deployment-logs
3
+ *
4
+ * Streams deployment log events to the client via Server-Sent Events.
5
+ * Subscribes to the DeploymentService EventEmitter for real-time log
6
+ * entries, filtered by targetId.
7
+ *
8
+ * - Accepts ?targetId query parameter (required)
9
+ * - Sends log entries as SSE "log" events
10
+ * - Sends heartbeat comments every 30 seconds to keep connection alive
11
+ * - Cleans up EventEmitter subscription on client disconnect
12
+ */
13
+ import { resolve } from '../../../lib/server-container.js';
14
+ // Force dynamic — SSE streams must never be statically optimized or cached
15
+ export const dynamic = 'force-dynamic';
16
+ const HEARTBEAT_INTERVAL_MS = 30_000;
17
+ export function GET(request) {
18
+ try {
19
+ const url = new URL(request.url);
20
+ const targetId = url.searchParams.get('targetId');
21
+ if (!targetId?.trim()) {
22
+ const errorStream = new ReadableStream({
23
+ start(controller) {
24
+ const encoder = new TextEncoder();
25
+ controller.enqueue(encoder.encode(`event: error\ndata: ${JSON.stringify({ error: 'targetId is required' })}\n\n`));
26
+ controller.close();
27
+ },
28
+ });
29
+ return new Response(errorStream, {
30
+ headers: {
31
+ 'Content-Type': 'text/event-stream',
32
+ 'Cache-Control': 'no-cache',
33
+ Connection: 'keep-alive',
34
+ },
35
+ });
36
+ }
37
+ const stream = new ReadableStream({
38
+ start(controller) {
39
+ const encoder = new TextEncoder();
40
+ let stopped = false;
41
+ function enqueue(text) {
42
+ if (stopped)
43
+ return;
44
+ try {
45
+ controller.enqueue(encoder.encode(text));
46
+ }
47
+ catch {
48
+ // Stream may be closed
49
+ }
50
+ }
51
+ const deploymentService = resolve('IDeploymentService');
52
+ // Subscribe to log events, filtering by targetId
53
+ const logHandler = (entry) => {
54
+ if (entry.targetId !== targetId)
55
+ return;
56
+ enqueue(`event: log\ndata: ${JSON.stringify(entry)}\n\n`);
57
+ };
58
+ deploymentService.on('log', logHandler);
59
+ // Heartbeat to keep connection alive
60
+ const heartbeatInterval = setInterval(() => {
61
+ enqueue(': heartbeat\n\n');
62
+ }, HEARTBEAT_INTERVAL_MS);
63
+ // Cleanup on client disconnect
64
+ const cleanup = () => {
65
+ stopped = true;
66
+ deploymentService.off('log', logHandler);
67
+ clearInterval(heartbeatInterval);
68
+ try {
69
+ controller.close();
70
+ }
71
+ catch {
72
+ // Stream may already be closed
73
+ }
74
+ };
75
+ request.signal.addEventListener('abort', cleanup, { once: true });
76
+ },
77
+ });
78
+ return new Response(stream, {
79
+ headers: {
80
+ 'Content-Type': 'text/event-stream',
81
+ 'Cache-Control': 'no-cache',
82
+ Connection: 'keep-alive',
83
+ },
84
+ });
85
+ }
86
+ catch (error) {
87
+ // eslint-disable-next-line no-console
88
+ console.error('[SSE route] GET /api/deployment-logs error:', error);
89
+ return new Response(JSON.stringify({ error: String(error) }), {
90
+ status: 500,
91
+ headers: { 'Content-Type': 'application/json' },
92
+ });
93
+ }
94
+ }
@@ -44,10 +44,10 @@ export function BaseDrawer({ open, onClose, modal = false, size, header, childre
44
44
  return (_jsxs(Drawer, { direction: "right", modal: modal, handleOnly: true, open: open, onOpenChange: (isOpen) => {
45
45
  if (!isOpen)
46
46
  onClose();
47
- }, children: [modal ? _jsx(DrawerOverlay, {}) : null, _jsxs(DrawerContent, { ref: contentRef, direction: "right", showCloseButton: false, className: cn(drawerVariants({ size }), className), "data-testid": testId, children: [_jsxs("button", { type: "button", "aria-label": "Close", onClick: onClose, className: "ring-offset-background focus:ring-ring absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden", "data-testid": testId ? `${testId}-close-button` : undefined, children: [_jsx(XIcon, { className: "size-4" }), _jsx("span", { className: "sr-only", children: "Close" })] }), header ? _jsx(DrawerHeader, { className: "shrink-0", children: header }) : null, header ? _jsx(Separator, {}) : null, featureFlags.envDeploy && deployTarget ? _jsx(DeployBar, { deployTarget: deployTarget }) : null, _jsx("div", { className: "flex min-h-0 flex-1 flex-col", children: children }), footer ? _jsx(DrawerFooter, { className: "shrink-0", children: footer }) : null] })] }));
47
+ }, children: [modal ? _jsx(DrawerOverlay, {}) : null, _jsxs(DrawerContent, { ref: contentRef, direction: "right", showCloseButton: false, className: cn(drawerVariants({ size }), className), "data-testid": testId, children: [_jsxs("button", { type: "button", "aria-label": "Close", onClick: onClose, className: "ring-offset-background focus:ring-ring absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden", "data-testid": testId ? `${testId}-close-button` : undefined, children: [_jsx(XIcon, { className: "size-4" }), _jsx("span", { className: "sr-only", children: "Close" })] }), header ? _jsx(DrawerHeader, { className: "shrink-0", children: header }) : null, header ? _jsx(Separator, {}) : null, featureFlags.envDeploy && deployTarget ? _jsx(DeployBar, { deployTarget: deployTarget }) : null, _jsx("div", { className: "flex min-h-0 flex-1 flex-col overflow-hidden", children: children }), footer ? _jsx(DrawerFooter, { className: "shrink-0", children: footer }) : null] })] }));
48
48
  }
49
49
  function DeployBar({ deployTarget }) {
50
50
  const deployAction = useDeployAction(deployTarget);
51
51
  const isDeploymentActive = deployAction.status === 'Booting' || deployAction.status === 'Ready';
52
- return (_jsxs("div", { "data-testid": "base-drawer-deploy-bar", className: "flex items-center gap-2 px-4 pb-3", children: [_jsx(TooltipProvider, { children: _jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx("span", { children: _jsx(ActionButton, { label: isDeploymentActive ? 'Stop Dev Server' : 'Start Dev Server', onClick: isDeploymentActive ? deployAction.stop : deployAction.deploy, loading: deployAction.deployLoading || deployAction.stopLoading, error: !!deployAction.deployError, icon: isDeploymentActive ? Square : Play, iconOnly: true, variant: "outline", size: "icon-sm" }) }) }), _jsx(TooltipContent, { children: isDeploymentActive ? 'Stop Dev Server' : 'Start Dev Server' })] }) }), isDeploymentActive ? (_jsx(DeploymentStatusBadge, { status: deployAction.status, url: deployAction.url })) : null] }));
52
+ return (_jsxs("div", { "data-testid": "base-drawer-deploy-bar", className: "flex items-center gap-2 px-4 pb-3", children: [_jsx(TooltipProvider, { children: _jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx("span", { children: _jsx(ActionButton, { label: isDeploymentActive ? 'Stop Dev Server' : 'Start Dev Server', onClick: isDeploymentActive ? deployAction.stop : deployAction.deploy, loading: deployAction.deployLoading || deployAction.stopLoading, error: !!deployAction.deployError, icon: isDeploymentActive ? Square : Play, iconOnly: true, variant: "outline", size: "icon-sm" }) }) }), _jsx(TooltipContent, { children: isDeploymentActive ? 'Stop Dev Server' : 'Start Dev Server' })] }) }), isDeploymentActive ? (_jsx(DeploymentStatusBadge, { status: deployAction.status, url: deployAction.url, targetId: deployTarget.targetId })) : null] }));
53
53
  }
@@ -75,5 +75,5 @@ export const WithDevServerBar = {
75
75
  * Try scrolling the content below to see the fixed header behavior.
76
76
  */
77
77
  export const ScrollableContent = {
78
- render: () => (_jsx(DrawerTrigger, { header: _jsxs(_Fragment, { children: [_jsx(DrawerTitle, { children: "Scrollable Content" }), _jsx(DrawerDescription, { children: "Content below overflows and scrolls" })] }), children: _jsx("div", { className: "flex flex-col gap-4 p-4", children: Array.from({ length: 30 }, (_, i) => (_jsxs("div", { className: "border-border rounded-md border p-3", children: [_jsxs("h4", { className: "text-sm font-medium", children: ["Item ", i + 1] }), _jsx("p", { className: "text-muted-foreground text-xs", children: "This is a scrollable content item to demonstrate overflow handling." })] }, i))) }) })),
78
+ render: () => (_jsx(DrawerTrigger, { header: _jsxs(_Fragment, { children: [_jsx(DrawerTitle, { children: "Scrollable Content" }), _jsx(DrawerDescription, { children: "Content below overflows and scrolls" })] }), children: _jsx("div", { className: "flex flex-col gap-4 overflow-y-auto p-4", children: Array.from({ length: 30 }, (_, i) => (_jsxs("div", { className: "border-border rounded-md border p-3", children: [_jsxs("h4", { className: "text-sm font-medium", children: ["Item ", i + 1] }), _jsx("p", { className: "text-muted-foreground text-xs", children: "This is a scrollable content item to demonstrate overflow handling." })] }, i))) }) })),
79
79
  };
@@ -1 +1 @@
1
- {"version":3,"file":"control-center-drawer.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/components/common/control-center-drawer/control-center-drawer.tsx"],"names":[],"mappings":"AA8DA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACtF,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAEhD,MAAM,WAAW,wBAAwB;IACvC,IAAI,EAAE,UAAU,GAAG,IAAI,CAAC;IACxB,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,QAAQ,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,cAAc,EAAE,CAAC,IAAI,EAAE,oBAAoB,KAAK,IAAI,CAAC;IACrD,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AAED,wBAAgB,mBAAmB,CAAC,EAClC,IAAI,EACJ,OAAO,EACP,QAAQ,EACR,UAAU,EACV,cAAc,EACd,YAAY,GACb,EAAE,wBAAwB,2CAqhB1B"}
1
+ {"version":3,"file":"control-center-drawer.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/components/common/control-center-drawer/control-center-drawer.tsx"],"names":[],"mappings":"AA+DA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACtF,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAEhD,MAAM,WAAW,wBAAwB;IACvC,IAAI,EAAE,UAAU,GAAG,IAAI,CAAC;IACxB,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,QAAQ,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,cAAc,EAAE,CAAC,IAAI,EAAE,oBAAoB,KAAK,IAAI,CAAC;IACrD,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AAED,wBAAgB,mBAAmB,CAAC,EAClC,IAAI,EACJ,OAAO,EACP,QAAQ,EACR,UAAU,EACV,cAAc,EACd,YAAY,GACb,EAAE,wBAAwB,2CA0jB1B"}
@@ -27,7 +27,7 @@ import { ActionButton } from '../../common/action-button/index.js';
27
27
  import { OpenActionMenu } from '../../common/open-action-menu/index.js';
28
28
  import { FeatureCreateDrawer } from '../../common/feature-create-drawer/index.js';
29
29
  import { PrdQuestionnaire } from '../../common/prd-questionnaire/index.js';
30
- import { TechDecisionsReview } from '../../common/tech-decisions-review/index.js';
30
+ import { TechReviewTabs } from '../../common/tech-review-tabs/index.js';
31
31
  import { MergeReview } from '../../common/merge-review/index.js';
32
32
  import { featureNodeStateConfig, lifecycleDisplayLabels } from '../../common/feature-node/index.js';
33
33
  import { useFeatureActions } from '../../common/feature-drawer/use-feature-actions.js';
@@ -40,6 +40,9 @@ export function ControlCenterDrawer({ view, onClose, onDelete, isDeleting, onCre
40
40
  // ── Tech decisions state ────────────────────────────────────────────────
41
41
  const [techData, setTechData] = useState(null);
42
42
  const [isLoadingTech, setIsLoadingTech] = useState(false);
43
+ // ── Product decisions state (for tech review Product tab) ─────────────
44
+ const [techProductData, setTechProductData] = useState(undefined);
45
+ const [isLoadingTechProduct, setIsLoadingTechProduct] = useState(false);
43
46
  // ── Merge review state ──────────────────────────────────────────────────
44
47
  const [mergeData, setMergeData] = useState(null);
45
48
  const [isLoadingMerge, setIsLoadingMerge] = useState(false);
@@ -116,6 +119,33 @@ export function ControlCenterDrawer({ view, onClose, onDelete, isDeleting, onCre
116
119
  cancelled = true;
117
120
  };
118
121
  }, [techFeatureId]);
122
+ // Fetch product decisions for the Product tab in tech review
123
+ useEffect(() => {
124
+ setTechProductData(undefined);
125
+ if (!techFeatureId)
126
+ return;
127
+ let cancelled = false;
128
+ setIsLoadingTechProduct(true);
129
+ getFeatureArtifact(techFeatureId)
130
+ .then((result) => {
131
+ if (cancelled)
132
+ return;
133
+ if (result.productDecisions) {
134
+ setTechProductData(result.productDecisions);
135
+ }
136
+ // Silent failure — product tab shows "not available"
137
+ })
138
+ .catch(() => {
139
+ // Silent failure — the product tab is supplementary
140
+ })
141
+ .finally(() => {
142
+ if (!cancelled)
143
+ setIsLoadingTechProduct(false);
144
+ });
145
+ return () => {
146
+ cancelled = true;
147
+ };
148
+ }, [techFeatureId]);
119
149
  const mergeFeatureId = view?.type === 'merge-review' ? view.node.featureId : null;
120
150
  useEffect(() => {
121
151
  setMergeData(null);
@@ -253,7 +283,7 @@ export function ControlCenterDrawer({ view, onClose, onDelete, isDeleting, onCre
253
283
  // ── Header ──────────────────────────────────────────────────────────────
254
284
  let header = undefined;
255
285
  if (featureNode) {
256
- header = (_jsxs(_Fragment, { children: [_jsxs("div", { "data-testid": "feature-drawer-header", children: [_jsx(DrawerTitle, { children: featureNode.name }), featureNode.description ? (_jsx(DrawerDescription, { children: featureNode.description })) : featureNode.featureId ? (_jsx(DrawerDescription, { className: "sr-only", children: featureNode.featureId })) : null] }), featureActionsInput ? (_jsxs("div", { className: "flex items-center gap-2 pt-2", children: [_jsx(OpenActionMenu, { actions: featureActions, repositoryPath: featureActionsInput.repositoryPath, showSpecs: !!featureActionsInput.specPath }), featureFlags.envDeploy && featureDeployTarget ? (_jsxs(_Fragment, { children: [_jsx(TooltipProvider, { children: _jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx("span", { children: _jsx(ActionButton, { label: isFeatureDeployActive ? 'Stop Dev Server' : 'Start Dev Server', onClick: isFeatureDeployActive ? deployAction.stop : deployAction.deploy, loading: deployAction.deployLoading || deployAction.stopLoading, error: !!deployAction.deployError, icon: isFeatureDeployActive ? Square : Play, iconOnly: true, variant: "outline", size: "icon-sm" }) }) }), _jsx(TooltipContent, { children: isFeatureDeployActive ? 'Stop Dev Server' : 'Start Dev Server' })] }) }), isFeatureDeployActive ? (_jsx(DeploymentStatusBadge, { status: deployAction.status, url: deployAction.url })) : null] })) : null, onDelete && featureNode.featureId ? (_jsxs(AlertDialog, { children: [_jsx(AlertDialogTrigger, { asChild: true, children: _jsx(Button, { variant: "ghost", size: "icon-sm", "aria-label": "Delete feature", disabled: isDeleting, className: "text-muted-foreground hover:text-destructive ml-auto", "data-testid": "feature-drawer-delete", children: isDeleting ? (_jsx(Loader2, { className: "size-4 animate-spin" })) : (_jsx(Trash2, { className: "size-4" })) }) }), _jsxs(AlertDialogContent, { children: [_jsxs(AlertDialogHeader, { children: [_jsx(AlertDialogTitle, { children: "Delete feature?" }), _jsxs(AlertDialogDescription, { children: ["This will permanently delete ", _jsx("strong", { children: featureNode.name }), " (", featureNode.featureId, "). This action cannot be undone.", featureNode.state === 'running' ? (_jsx(_Fragment, { children: " This feature has a running agent that will be stopped." })) : null] })] }), _jsxs(AlertDialogFooter, { children: [_jsx(AlertDialogCancel, { disabled: isDeleting, children: "Cancel" }), _jsx(AlertDialogAction, { variant: "destructive", disabled: isDeleting, onClick: () => onDelete(featureNode.featureId), children: isDeleting ? (_jsxs(_Fragment, { children: [_jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }), "Deleting\u2026"] })) : ('Delete') })] })] })] })) : null] })) : null] }));
286
+ header = (_jsxs(_Fragment, { children: [_jsxs("div", { "data-testid": "feature-drawer-header", children: [_jsx(DrawerTitle, { children: featureNode.name }), featureNode.description ? (_jsx(DrawerDescription, { children: featureNode.description })) : featureNode.featureId ? (_jsx(DrawerDescription, { className: "sr-only", children: featureNode.featureId })) : null] }), featureActionsInput ? (_jsxs("div", { className: "flex items-center gap-2 pt-2", children: [_jsx(OpenActionMenu, { actions: featureActions, repositoryPath: featureActionsInput.repositoryPath, showSpecs: !!featureActionsInput.specPath }), featureFlags.envDeploy && featureDeployTarget ? (_jsxs(_Fragment, { children: [_jsx(TooltipProvider, { children: _jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx("span", { children: _jsx(ActionButton, { label: isFeatureDeployActive ? 'Stop Dev Server' : 'Start Dev Server', onClick: isFeatureDeployActive ? deployAction.stop : deployAction.deploy, loading: deployAction.deployLoading || deployAction.stopLoading, error: !!deployAction.deployError, icon: isFeatureDeployActive ? Square : Play, iconOnly: true, variant: "outline", size: "icon-sm" }) }) }), _jsx(TooltipContent, { children: isFeatureDeployActive ? 'Stop Dev Server' : 'Start Dev Server' })] }) }), isFeatureDeployActive ? (_jsx(DeploymentStatusBadge, { status: deployAction.status, url: deployAction.url, targetId: featureDeployTarget?.targetId })) : null] })) : null, onDelete && featureNode.featureId ? (_jsxs(AlertDialog, { children: [_jsx(AlertDialogTrigger, { asChild: true, children: _jsx(Button, { variant: "ghost", size: "icon-sm", "aria-label": "Delete feature", disabled: isDeleting, className: "text-muted-foreground hover:text-destructive ml-auto", "data-testid": "feature-drawer-delete", children: isDeleting ? (_jsx(Loader2, { className: "size-4 animate-spin" })) : (_jsx(Trash2, { className: "size-4" })) }) }), _jsxs(AlertDialogContent, { children: [_jsxs(AlertDialogHeader, { children: [_jsx(AlertDialogTitle, { children: "Delete feature?" }), _jsxs(AlertDialogDescription, { children: ["This will permanently delete ", _jsx("strong", { children: featureNode.name }), " (", featureNode.featureId, "). This action cannot be undone.", featureNode.state === 'running' ? (_jsx(_Fragment, { children: " This feature has a running agent that will be stopped." })) : null] })] }), _jsxs(AlertDialogFooter, { children: [_jsx(AlertDialogCancel, { disabled: isDeleting, children: "Cancel" }), _jsx(AlertDialogAction, { variant: "destructive", disabled: isDeleting, onClick: () => onDelete(featureNode.featureId), children: isDeleting ? (_jsxs(_Fragment, { children: [_jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }), "Deleting\u2026"] })) : ('Delete') })] })] })] })) : null] })) : null] }));
257
287
  }
258
288
  else if (repoData) {
259
289
  header = (_jsxs("div", { "data-testid": "repository-drawer-header", children: [_jsx(DrawerTitle, { children: repoData.name }), repoData.repositoryPath ? (_jsx(DrawerDescription, { className: "truncate font-mono text-xs", children: repoData.repositoryPath })) : null] }));
@@ -267,13 +297,13 @@ export function ControlCenterDrawer({ view, onClose, onDelete, isDeleting, onCre
267
297
  body = prdData ? (_jsx(PrdQuestionnaire, { data: prdData, selections: prdSelections, onSelect: (qId, oId) => setPrdSelections((prev) => ({ ...prev, [qId]: oId })), onApprove: handlePrdApprove, onReject: handlePrdReject, isProcessing: isLoadingPrd, isRejecting: isRejecting })) : (_jsx("div", { className: "flex items-center justify-center p-8", children: _jsx(Loader2, { className: "text-muted-foreground h-6 w-6 animate-spin" }) }));
268
298
  }
269
299
  else if (view?.type === 'tech-review') {
270
- body = techData ? (_jsx(TechDecisionsReview, { data: techData, onApprove: handleTechApprove, onReject: handleTechReject, isProcessing: isLoadingTech, isRejecting: isRejecting })) : (_jsx("div", { className: "flex items-center justify-center p-8", children: _jsx(Loader2, { className: "text-muted-foreground h-6 w-6 animate-spin" }) }));
300
+ body = techData ? (_jsx(TechReviewTabs, { techData: techData, productData: isLoadingTechProduct ? null : techProductData, onApprove: handleTechApprove, onReject: handleTechReject, isProcessing: isLoadingTech, isRejecting: isRejecting })) : (_jsx("div", { className: "flex items-center justify-center p-8", children: _jsx(Loader2, { className: "text-muted-foreground h-6 w-6 animate-spin" }) }));
271
301
  }
272
302
  else if (view?.type === 'merge-review') {
273
303
  body = mergeData ? (_jsx(MergeReview, { data: mergeData, onApprove: handleMergeApprove, onReject: handleMergeReject, isProcessing: isLoadingMerge, isRejecting: isRejecting })) : (_jsx("div", { className: "flex items-center justify-center p-8", children: _jsx(Loader2, { className: "text-muted-foreground h-6 w-6 animate-spin" }) }));
274
304
  }
275
305
  else if (view?.type === 'repository' && repoData?.repositoryPath) {
276
- body = (_jsxs(_Fragment, { children: [_jsx(Separator, {}), _jsxs("div", { className: "flex flex-col gap-3 p-4", children: [_jsx("div", { className: "text-muted-foreground text-xs font-semibold tracking-wider", children: "OPEN WITH" }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(ActionButton, { label: "Open in IDE", onClick: repoActions.openInIde, loading: repoActions.ideLoading, error: !!repoActions.ideError, icon: Code2, variant: "outline", size: "sm" }), _jsx(ActionButton, { label: "Open in Shell", onClick: repoActions.openInShell, loading: repoActions.shellLoading, error: !!repoActions.shellError, icon: Terminal, variant: "outline", size: "sm" }), _jsx(ActionButton, { label: "Open Folder", onClick: repoActions.openFolder, loading: repoActions.folderLoading, error: !!repoActions.folderError, icon: FolderOpen, variant: "outline", size: "sm" })] })] })] }));
306
+ body = (_jsxs("div", { className: "flex-1 overflow-y-auto", children: [_jsx(Separator, {}), _jsxs("div", { className: "flex flex-col gap-3 p-4", children: [_jsx("div", { className: "text-muted-foreground text-xs font-semibold tracking-wider", children: "OPEN WITH" }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(ActionButton, { label: "Open in IDE", onClick: repoActions.openInIde, loading: repoActions.ideLoading, error: !!repoActions.ideError, icon: Code2, variant: "outline", size: "sm" }), _jsx(ActionButton, { label: "Open in Shell", onClick: repoActions.openInShell, loading: repoActions.shellLoading, error: !!repoActions.shellError, icon: Terminal, variant: "outline", size: "sm" }), _jsx(ActionButton, { label: "Open Folder", onClick: repoActions.openFolder, loading: repoActions.folderLoading, error: !!repoActions.folderError, icon: FolderOpen, variant: "outline", size: "sm" })] })] })] }));
277
307
  }
278
308
  // ── Render ──────────────────────────────────────────────────────────────
279
309
  return (_jsxs(_Fragment, { children: [_jsx(BaseDrawer, { open: view !== null && !isCreateView, onClose: onClose, size: "md", modal: false, header: header, deployTarget: repoDeployTarget, "data-testid": view?.type === 'feature'
@@ -2,6 +2,7 @@ import { DeploymentState } from '../../../../../../packages/core/src/domain/gene
2
2
  export interface DeploymentStatusBadgeProps {
3
3
  status: DeploymentState | null;
4
4
  url?: string | null;
5
+ targetId?: string;
5
6
  }
6
- export declare function DeploymentStatusBadge({ status, url }: DeploymentStatusBadgeProps): import("react/jsx-runtime").JSX.Element | null;
7
+ export declare function DeploymentStatusBadge({ status, url, targetId }: DeploymentStatusBadgeProps): import("react/jsx-runtime").JSX.Element | null;
7
8
  //# sourceMappingURL=deployment-status-badge.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"deployment-status-badge.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/components/common/deployment-status-badge/deployment-status-badge.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,sCAAsC,CAAC;AAGvE,MAAM,WAAW,0BAA0B;IACzC,MAAM,EAAE,eAAe,GAAG,IAAI,CAAC;IAC/B,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACrB;AAED,wBAAgB,qBAAqB,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,EAAE,0BAA0B,kDAgChF"}
1
+ {"version":3,"file":"deployment-status-badge.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/components/common/deployment-status-badge/deployment-status-badge.tsx"],"names":[],"mappings":"AAIA,OAAO,EAAE,eAAe,EAAE,MAAM,sCAAsC,CAAC;AAIvE,MAAM,WAAW,0BAA0B;IACzC,MAAM,EAAE,eAAe,GAAG,IAAI,CAAC;IAC/B,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,wBAAgB,qBAAqB,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,QAAQ,EAAE,EAAE,0BAA0B,kDAgF1F"}