@shepai/cli 1.26.0 → 1.28.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 (225) hide show
  1. package/dist/packages/core/src/application/ports/output/index.d.ts +1 -1
  2. package/dist/packages/core/src/application/ports/output/index.d.ts.map +1 -1
  3. package/dist/packages/core/src/application/ports/output/services/index.d.ts +1 -0
  4. package/dist/packages/core/src/application/ports/output/services/index.d.ts.map +1 -1
  5. package/dist/packages/core/src/application/ports/output/services/notification-service.interface.d.ts +29 -0
  6. package/dist/packages/core/src/application/ports/output/services/notification-service.interface.d.ts.map +1 -0
  7. package/dist/packages/core/src/application/ports/output/services/notification-service.interface.js +12 -0
  8. package/dist/packages/core/src/application/use-cases/features/create/metadata-generator.d.ts +3 -2
  9. package/dist/packages/core/src/application/use-cases/features/create/metadata-generator.d.ts.map +1 -1
  10. package/dist/packages/core/src/application/use-cases/features/create/metadata-generator.js +36 -15
  11. package/dist/packages/core/src/domain/factories/settings-defaults.factory.d.ts.map +1 -1
  12. package/dist/packages/core/src/domain/factories/settings-defaults.factory.js +13 -0
  13. package/dist/packages/core/src/domain/generated/output.d.ts +109 -0
  14. package/dist/packages/core/src/domain/generated/output.d.ts.map +1 -1
  15. package/dist/packages/core/src/domain/generated/output.js +15 -0
  16. package/dist/packages/core/src/infrastructure/di/container.d.ts.map +1 -1
  17. package/dist/packages/core/src/infrastructure/di/container.js +17 -0
  18. package/dist/packages/core/src/infrastructure/persistence/sqlite/mappers/settings.mapper.d.ts +8 -0
  19. package/dist/packages/core/src/infrastructure/persistence/sqlite/mappers/settings.mapper.d.ts.map +1 -1
  20. package/dist/packages/core/src/infrastructure/persistence/sqlite/mappers/settings.mapper.js +22 -0
  21. package/dist/packages/core/src/infrastructure/persistence/sqlite/migrations.d.ts.map +1 -1
  22. package/dist/packages/core/src/infrastructure/persistence/sqlite/migrations.js +14 -0
  23. package/dist/packages/core/src/infrastructure/repositories/sqlite-settings.repository.d.ts.map +1 -1
  24. package/dist/packages/core/src/infrastructure/repositories/sqlite-settings.repository.js +17 -3
  25. package/dist/packages/core/src/infrastructure/services/notifications/desktop-notifier.d.ts +22 -0
  26. package/dist/packages/core/src/infrastructure/services/notifications/desktop-notifier.d.ts.map +1 -0
  27. package/dist/packages/core/src/infrastructure/services/notifications/desktop-notifier.js +39 -0
  28. package/dist/packages/core/src/infrastructure/services/notifications/notification-bus.d.ts +52 -0
  29. package/dist/packages/core/src/infrastructure/services/notifications/notification-bus.d.ts.map +1 -0
  30. package/dist/packages/core/src/infrastructure/services/notifications/notification-bus.js +64 -0
  31. package/dist/packages/core/src/infrastructure/services/notifications/notification-watcher.service.d.ts +60 -0
  32. package/dist/packages/core/src/infrastructure/services/notifications/notification-watcher.service.d.ts.map +1 -0
  33. package/dist/packages/core/src/infrastructure/services/notifications/notification-watcher.service.js +225 -0
  34. package/dist/packages/core/src/infrastructure/services/notifications/notification.service.d.ts +21 -0
  35. package/dist/packages/core/src/infrastructure/services/notifications/notification.service.d.ts.map +1 -0
  36. package/dist/packages/core/src/infrastructure/services/notifications/notification.service.js +43 -0
  37. package/dist/src/presentation/cli/commands/feat/new.command.d.ts.map +1 -1
  38. package/dist/src/presentation/cli/commands/feat/new.command.js +7 -6
  39. package/dist/src/presentation/cli/commands/ui.command.d.ts.map +1 -1
  40. package/dist/src/presentation/cli/commands/ui.command.js +8 -0
  41. package/dist/src/presentation/web/app/api/agent-events/route.d.ts +14 -0
  42. package/dist/src/presentation/web/app/api/agent-events/route.d.ts.map +1 -0
  43. package/dist/src/presentation/web/app/api/agent-events/route.js +77 -0
  44. package/dist/src/presentation/web/app/api/features/create/route.d.ts.map +1 -1
  45. package/dist/src/presentation/web/app/api/features/create/route.js +28 -2
  46. package/dist/src/presentation/web/app/page.d.ts.map +1 -1
  47. package/dist/src/presentation/web/app/page.js +9 -12
  48. package/dist/src/presentation/web/components/common/feature-create-drawer/feature-create-drawer.d.ts +4 -8
  49. package/dist/src/presentation/web/components/common/feature-create-drawer/feature-create-drawer.d.ts.map +1 -1
  50. package/dist/src/presentation/web/components/common/feature-create-drawer/feature-create-drawer.js +37 -4
  51. package/dist/src/presentation/web/components/common/feature-create-drawer/feature-create-drawer.stories.d.ts +30 -6
  52. package/dist/src/presentation/web/components/common/feature-create-drawer/feature-create-drawer.stories.d.ts.map +1 -1
  53. package/dist/src/presentation/web/components/common/feature-create-drawer/feature-create-drawer.stories.js +74 -18
  54. package/dist/src/presentation/web/components/common/feature-create-drawer/index.d.ts +1 -1
  55. package/dist/src/presentation/web/components/common/feature-create-drawer/index.d.ts.map +1 -1
  56. package/dist/src/presentation/web/components/common/feature-drawer/feature-drawer.js +2 -2
  57. package/dist/src/presentation/web/components/common/feature-drawer/feature-drawer.stories.js +3 -3
  58. package/dist/src/presentation/web/components/common/feature-node/feature-node-state-config.d.ts +5 -2
  59. package/dist/src/presentation/web/components/common/feature-node/feature-node-state-config.d.ts.map +1 -1
  60. package/dist/src/presentation/web/components/common/feature-node/feature-node-state-config.js +9 -0
  61. package/dist/src/presentation/web/components/common/feature-node/feature-node.d.ts.map +1 -1
  62. package/dist/src/presentation/web/components/common/feature-node/feature-node.js +9 -7
  63. package/dist/src/presentation/web/components/common/feature-node/feature-node.stories.d.ts.map +1 -1
  64. package/dist/src/presentation/web/components/common/feature-node/feature-node.stories.js +3 -0
  65. package/dist/src/presentation/web/components/common/index.d.ts +1 -1
  66. package/dist/src/presentation/web/components/common/index.d.ts.map +1 -1
  67. package/dist/src/presentation/web/components/common/index.js +1 -1
  68. package/dist/src/presentation/web/components/features/control-center/control-center-inner.d.ts.map +1 -1
  69. package/dist/src/presentation/web/components/features/control-center/control-center-inner.js +2 -2
  70. package/dist/src/presentation/web/components/features/control-center/use-control-center-state.d.ts +3 -2
  71. package/dist/src/presentation/web/components/features/control-center/use-control-center-state.d.ts.map +1 -1
  72. package/dist/src/presentation/web/components/features/control-center/use-control-center-state.js +4 -12
  73. package/dist/src/presentation/web/components/layouts/app-shell/app-shell.d.ts.map +1 -1
  74. package/dist/src/presentation/web/components/layouts/app-shell/app-shell.js +3 -0
  75. package/dist/src/presentation/web/components/ui/button.d.ts +1 -1
  76. package/dist/src/presentation/web/components/ui/checkbox-group-item.d.ts +11 -0
  77. package/dist/src/presentation/web/components/ui/checkbox-group-item.d.ts.map +1 -0
  78. package/dist/src/presentation/web/components/ui/checkbox-group-item.js +8 -0
  79. package/dist/src/presentation/web/components/ui/checkbox-group-item.stories.d.ts +17 -0
  80. package/dist/src/presentation/web/components/ui/checkbox-group-item.stories.d.ts.map +1 -0
  81. package/dist/src/presentation/web/components/ui/checkbox-group-item.stories.js +33 -0
  82. package/dist/src/presentation/web/components/ui/checkbox-group.d.ts +21 -0
  83. package/dist/src/presentation/web/components/ui/checkbox-group.d.ts.map +1 -0
  84. package/dist/src/presentation/web/components/ui/checkbox-group.js +24 -0
  85. package/dist/src/presentation/web/components/ui/checkbox-group.stories.d.ts +23 -0
  86. package/dist/src/presentation/web/components/ui/checkbox-group.stories.d.ts.map +1 -0
  87. package/dist/src/presentation/web/components/ui/checkbox-group.stories.js +61 -0
  88. package/dist/src/presentation/web/components/ui/checkbox.d.ts +5 -0
  89. package/dist/src/presentation/web/components/ui/checkbox.d.ts.map +1 -0
  90. package/dist/src/presentation/web/components/ui/checkbox.js +9 -0
  91. package/dist/src/presentation/web/components/ui/checkbox.stories.d.ts +10 -0
  92. package/dist/src/presentation/web/components/ui/checkbox.stories.d.ts.map +1 -0
  93. package/dist/src/presentation/web/components/ui/checkbox.stories.js +24 -0
  94. package/dist/src/presentation/web/hooks/use-agent-events.d.ts +12 -0
  95. package/dist/src/presentation/web/hooks/use-agent-events.d.ts.map +1 -0
  96. package/dist/src/presentation/web/hooks/use-agent-events.js +58 -0
  97. package/dist/src/presentation/web/hooks/use-notifications.d.ts +10 -0
  98. package/dist/src/presentation/web/hooks/use-notifications.d.ts.map +1 -0
  99. package/dist/src/presentation/web/hooks/use-notifications.js +57 -0
  100. package/dist/src/presentation/web/hooks/use-notifications.stories.d.ts +6 -0
  101. package/dist/src/presentation/web/hooks/use-notifications.stories.d.ts.map +1 -0
  102. package/dist/src/presentation/web/hooks/use-notifications.stories.js +62 -0
  103. package/dist/tsconfig.build.tsbuildinfo +1 -1
  104. package/package.json +3 -1
  105. package/web/.next/BUILD_ID +1 -1
  106. package/web/.next/app-path-routes-manifest.json +1 -0
  107. package/web/.next/build-manifest.json +2 -2
  108. package/web/.next/cache/.previewinfo +1 -1
  109. package/web/.next/cache/.rscinfo +1 -1
  110. package/web/.next/cache/.tsbuildinfo +1 -1
  111. package/web/.next/cache/config.json +3 -3
  112. package/web/.next/fallback-build-manifest.json +2 -2
  113. package/web/.next/prerender-manifest.json +3 -3
  114. package/web/.next/required-server-files.js +1 -1
  115. package/web/.next/required-server-files.json +1 -1
  116. package/web/.next/routes-manifest.json +6 -0
  117. package/web/.next/server/app/_global-error.html +2 -2
  118. package/web/.next/server/app/_global-error.rsc +1 -1
  119. package/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  120. package/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  121. package/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  122. package/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  123. package/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  124. package/web/.next/server/app/_not-found/page.js.nft.json +1 -1
  125. package/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  126. package/web/.next/server/app/_not-found.html +2 -2
  127. package/web/.next/server/app/_not-found.rsc +10 -10
  128. package/web/.next/server/app/_not-found.segments/_full.segment.rsc +10 -10
  129. package/web/.next/server/app/_not-found.segments/_head.segment.rsc +3 -3
  130. package/web/.next/server/app/_not-found.segments/_index.segment.rsc +6 -6
  131. package/web/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
  132. package/web/.next/server/app/_not-found.segments/_not-found.segment.rsc +3 -3
  133. package/web/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  134. package/web/.next/server/app/api/agent-events/route/app-paths-manifest.json +3 -0
  135. package/web/.next/server/app/api/agent-events/route/build-manifest.json +11 -0
  136. package/web/.next/server/app/api/agent-events/route/server-reference-manifest.json +4 -0
  137. package/web/.next/server/app/api/agent-events/route.js +6 -0
  138. package/web/.next/server/app/api/agent-events/route.js.map +5 -0
  139. package/web/.next/server/app/api/agent-events/route.js.nft.json +1 -0
  140. package/web/.next/server/app/api/agent-events/route_client-reference-manifest.js +2 -0
  141. package/web/.next/server/app/api/dialog/pick-files/route.js +3 -2
  142. package/web/.next/server/app/api/dialog/pick-files/route.js.nft.json +1 -1
  143. package/web/.next/server/app/api/dialog/pick-folder/route.js +3 -2
  144. package/web/.next/server/app/api/dialog/pick-folder/route.js.nft.json +1 -1
  145. package/web/.next/server/app/api/features/create/route.js +3 -2
  146. package/web/.next/server/app/api/features/create/route.js.nft.json +1 -1
  147. package/web/.next/server/app/api/ide/open/route.js +4 -2
  148. package/web/.next/server/app/api/ide/open/route.js.nft.json +1 -1
  149. package/web/.next/server/app/api/shell/open/route.js +3 -2
  150. package/web/.next/server/app/api/shell/open/route.js.nft.json +1 -1
  151. package/web/.next/server/app/page.js +1 -1
  152. package/web/.next/server/app/page.js.nft.json +1 -1
  153. package/web/.next/server/app/page_client-reference-manifest.js +1 -1
  154. package/web/.next/server/app/version/page.js.nft.json +1 -1
  155. package/web/.next/server/app/version/page_client-reference-manifest.js +1 -1
  156. package/web/.next/server/app/version.html +2 -2
  157. package/web/.next/server/app/version.rsc +5 -5
  158. package/web/.next/server/app/version.segments/_full.segment.rsc +5 -5
  159. package/web/.next/server/app/version.segments/_head.segment.rsc +1 -1
  160. package/web/.next/server/app/version.segments/_index.segment.rsc +4 -4
  161. package/web/.next/server/app/version.segments/_tree.segment.rsc +2 -2
  162. package/web/.next/server/app/version.segments/version/__PAGE__.segment.rsc +2 -2
  163. package/web/.next/server/app/version.segments/version.segment.rsc +1 -1
  164. package/web/.next/server/app-paths-manifest.json +1 -0
  165. package/web/.next/server/chunks/403f9_next_567de315._.js +17 -0
  166. package/web/.next/server/chunks/403f9_next_567de315._.js.map +1 -0
  167. package/web/.next/server/chunks/403f9_next_dist_esm_build_templates_app-route_12059e3e.js +3 -0
  168. package/web/.next/server/chunks/403f9_next_dist_esm_build_templates_app-route_12059e3e.js.map +1 -0
  169. package/web/.next/server/chunks/744ca_web__next-internal_server_app_api_agent-events_route_actions_686edb08.js +3 -0
  170. package/web/.next/server/chunks/744ca_web__next-internal_server_app_api_agent-events_route_actions_686edb08.js.map +1 -0
  171. package/web/.next/server/chunks/[root-of-the-server]__4e987cf3._.js +8 -0
  172. package/web/.next/server/chunks/[root-of-the-server]__4e987cf3._.js.map +1 -0
  173. package/web/.next/server/chunks/[root-of-the-server]__57104a4c._.js +7 -0
  174. package/web/.next/server/chunks/[root-of-the-server]__57104a4c._.js.map +1 -0
  175. package/web/.next/server/chunks/{[root-of-the-server]__602cce1b._.js → [root-of-the-server]__61349adc._.js} +2 -2
  176. package/web/.next/server/chunks/{[root-of-the-server]__bb4fc5b5._.js → [root-of-the-server]__a6bf88cc._.js} +2 -2
  177. package/web/.next/server/chunks/[root-of-the-server]__aa2074ca._.js +3 -0
  178. package/web/.next/server/chunks/[root-of-the-server]__aa2074ca._.js.map +1 -0
  179. package/web/.next/server/chunks/{[root-of-the-server]__1913013c._.js → [root-of-the-server]__ec87d735._.js} +2 -2
  180. package/web/.next/server/chunks/[root-of-the-server]__ecaf05bc._.js +3 -0
  181. package/web/.next/server/chunks/[root-of-the-server]__ecaf05bc._.js.map +1 -0
  182. package/web/.next/server/chunks/ssr/403f9_next_dist_esm_build_templates_app-page_dbf21e04.js +1 -1
  183. package/web/.next/server/chunks/ssr/[root-of-the-server]__80e5bed2._.js +1 -1
  184. package/web/.next/server/chunks/ssr/[root-of-the-server]__f12b60be._.js +1 -1
  185. package/web/.next/server/chunks/ssr/[root-of-the-server]__fa87595c._.js +1 -1
  186. package/web/.next/server/chunks/ssr/_09afa42a._.js +6 -1
  187. package/web/.next/server/chunks/ssr/_09afa42a._.js.map +1 -1
  188. package/web/.next/server/chunks/ssr/_884ddade._.js +1 -1
  189. package/web/.next/server/chunks/ssr/_a4189414._.js +10 -0
  190. package/web/.next/server/chunks/ssr/_a4189414._.js.map +1 -0
  191. package/web/.next/server/chunks/ssr/_c5d377cc._.js +1 -1
  192. package/web/.next/server/chunks/ssr/src_presentation_web_components_e599bb8c._.js +1 -1
  193. package/web/.next/server/chunks/ssr/src_presentation_web_components_e599bb8c._.js.map +1 -1
  194. package/web/.next/server/chunks/ssr/src_presentation_web_ed0934e5._.js +1 -1
  195. package/web/.next/server/chunks/ssr/src_presentation_web_ed0934e5._.js.map +1 -1
  196. package/web/.next/server/pages/404.html +2 -2
  197. package/web/.next/server/pages/500.html +2 -2
  198. package/web/.next/server/server-reference-manifest.js +1 -1
  199. package/web/.next/server/server-reference-manifest.json +1 -1
  200. package/web/.next/static/chunks/1d665ddc2230f73f.js +5 -0
  201. package/web/.next/static/chunks/{c2854b6c6d0ca1f3.js → 28c05929f2636c01.js} +7 -2
  202. package/web/.next/static/chunks/a7273c84449394e0.css +2 -0
  203. package/web/.next/static/chunks/b32b17f102226f32.js +1 -0
  204. package/web/.next/trace +1 -1
  205. package/web/.next/trace-build +1 -1
  206. package/web/.next/types/link.d.ts +1 -0
  207. package/web/.next/types/routes.d.ts +2 -1
  208. package/web/.next/types/validator.ts +9 -0
  209. package/web/.next/server/chunks/[root-of-the-server]__56ac1509._.js +0 -3
  210. package/web/.next/server/chunks/[root-of-the-server]__56ac1509._.js.map +0 -1
  211. package/web/.next/server/chunks/[root-of-the-server]__ba6c5641._.js +0 -21
  212. package/web/.next/server/chunks/[root-of-the-server]__ba6c5641._.js.map +0 -1
  213. package/web/.next/server/chunks/[root-of-the-server]__eaa478b7._.js +0 -8
  214. package/web/.next/server/chunks/[root-of-the-server]__eaa478b7._.js.map +0 -1
  215. package/web/.next/server/chunks/ssr/_1c3962d7._.js +0 -10
  216. package/web/.next/server/chunks/ssr/_1c3962d7._.js.map +0 -1
  217. package/web/.next/static/chunks/78ef4acb26e4d6ea.js +0 -1
  218. package/web/.next/static/chunks/79009d1ca0d944d2.js +0 -5
  219. package/web/.next/static/chunks/86ff2b6444e06e88.css +0 -2
  220. /package/web/.next/server/chunks/{[root-of-the-server]__602cce1b._.js.map → [root-of-the-server]__61349adc._.js.map} +0 -0
  221. /package/web/.next/server/chunks/{[root-of-the-server]__bb4fc5b5._.js.map → [root-of-the-server]__a6bf88cc._.js.map} +0 -0
  222. /package/web/.next/server/chunks/{[root-of-the-server]__1913013c._.js.map → [root-of-the-server]__ec87d735._.js.map} +0 -0
  223. /package/web/.next/static/{_6j6GS-S6zPiASj5tsxJ8 → wymVMk3X_cTuoYKeS0Gbf}/_buildManifest.js +0 -0
  224. /package/web/.next/static/{_6j6GS-S6zPiASj5tsxJ8 → wymVMk3X_cTuoYKeS0Gbf}/_clientMiddlewareManifest.json +0 -0
  225. /package/web/.next/static/{_6j6GS-S6zPiASj5tsxJ8 → wymVMk3X_cTuoYKeS0Gbf}/_ssgManifest.js +0 -0
@@ -0,0 +1,77 @@
1
+ /**
2
+ * SSE API Route: GET /api/agent-events
3
+ *
4
+ * Streams agent lifecycle notification events to connected web UI clients
5
+ * via Server-Sent Events (SSE). This is the first API route in the web UI.
6
+ *
7
+ * - Subscribes to the notification event bus (shared in-process singleton)
8
+ * - Formats events as SSE data frames (event: notification\ndata: JSON\n\n)
9
+ * - Sends heartbeat comments every 30 seconds to keep connection alive
10
+ * - Supports optional ?runId query parameter to filter events
11
+ * - Cleans up listeners and intervals on client disconnect
12
+ */
13
+ import { getNotificationBus, hasNotificationBus, } from '../../../../../../packages/core/src/infrastructure/services/notifications/notification-bus.js';
14
+ const HEARTBEAT_INTERVAL_MS = 30_000;
15
+ function formatSSEEvent(event) {
16
+ return `event: notification\ndata: ${JSON.stringify(event)}\n\n`;
17
+ }
18
+ function formatHeartbeat() {
19
+ return ': heartbeat\n\n';
20
+ }
21
+ export function GET(request) {
22
+ const url = new URL(request.url);
23
+ const runIdFilter = url.searchParams.get('runId');
24
+ const stream = new ReadableStream({
25
+ start(controller) {
26
+ const encoder = new TextEncoder();
27
+ // Check if bus is available
28
+ if (!hasNotificationBus()) {
29
+ controller.close();
30
+ return;
31
+ }
32
+ const bus = getNotificationBus();
33
+ // Notification event listener
34
+ const onNotification = (event) => {
35
+ // Apply runId filter if set
36
+ if (runIdFilter && event.agentRunId !== runIdFilter) {
37
+ return;
38
+ }
39
+ try {
40
+ controller.enqueue(encoder.encode(formatSSEEvent(event)));
41
+ }
42
+ catch {
43
+ // Stream may be closed — ignore enqueue errors
44
+ }
45
+ };
46
+ bus.on('notification', onNotification);
47
+ // Heartbeat to keep connection alive
48
+ const heartbeatInterval = setInterval(() => {
49
+ try {
50
+ controller.enqueue(encoder.encode(formatHeartbeat()));
51
+ }
52
+ catch {
53
+ // Stream may be closed — ignore enqueue errors
54
+ }
55
+ }, HEARTBEAT_INTERVAL_MS);
56
+ // Cleanup on client disconnect
57
+ const cleanup = () => {
58
+ bus.removeListener('notification', onNotification);
59
+ clearInterval(heartbeatInterval);
60
+ try {
61
+ controller.close();
62
+ }
63
+ catch {
64
+ // Stream may already be closed
65
+ }
66
+ };
67
+ request.signal.addEventListener('abort', cleanup, { once: true });
68
+ },
69
+ });
70
+ return new Response(stream, {
71
+ headers: {
72
+ 'Content-Type': 'text/event-stream',
73
+ 'Cache-Control': 'no-cache',
74
+ Connection: 'keep-alive',
75
+ },
76
+ });
77
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"route.d.ts","sourceRoot":"","sources":["../../../../../../../../src/presentation/web/app/api/features/create/route.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AA2B3C,wBAAsB,IAAI,CAAC,OAAO,EAAE,OAAO;;kGA0B1C"}
1
+ {"version":3,"file":"route.d.ts","sourceRoot":"","sources":["../../../../../../../../src/presentation/web/app/api/features/create/route.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAqD3C,wBAAsB,IAAI,CAAC,OAAO,EAAE,OAAO;;kGAyC1C"}
@@ -1,5 +1,22 @@
1
1
  import { NextResponse } from 'next/server';
2
2
  import { createFeature } from '../../../../../../../packages/core/src/infrastructure/di/use-cases-bridge.js';
3
+ function isValidApprovalGates(value) {
4
+ if (typeof value !== 'object' || value === null || Array.isArray(value))
5
+ return false;
6
+ const obj = value;
7
+ if (typeof obj.allowPrd !== 'boolean' || typeof obj.allowPlan !== 'boolean')
8
+ return false;
9
+ if ('allowMerge' in obj && typeof obj.allowMerge !== 'boolean')
10
+ return false;
11
+ return true;
12
+ }
13
+ function normalizeApprovalGates(gates) {
14
+ return {
15
+ allowPrd: gates.allowPrd,
16
+ allowPlan: gates.allowPlan,
17
+ allowMerge: 'allowMerge' in gates ? gates.allowMerge : false,
18
+ };
19
+ }
3
20
  function composeUserInput(name, description, attachments) {
4
21
  let userInput = `Feature: ${name}`;
5
22
  if (description) {
@@ -13,16 +30,25 @@ function composeUserInput(name, description, attachments) {
13
30
  }
14
31
  export async function POST(request) {
15
32
  const body = await request.json();
16
- const { name, description, repositoryPath, attachments } = body;
33
+ const { name, description, repositoryPath, attachments, approvalGates } = body;
17
34
  if (!name?.trim()) {
18
35
  return NextResponse.json({ error: 'name is required' }, { status: 400 });
19
36
  }
20
37
  if (!repositoryPath?.trim()) {
21
38
  return NextResponse.json({ error: 'repositoryPath is required' }, { status: 400 });
22
39
  }
40
+ if (approvalGates !== undefined && !isValidApprovalGates(approvalGates)) {
41
+ return NextResponse.json({
42
+ error: 'approvalGates must be an object with boolean allowPrd, allowPlan, and optional boolean allowMerge',
43
+ }, { status: 400 });
44
+ }
23
45
  const userInput = composeUserInput(name, description, attachments);
46
+ const defaultGates = { allowPrd: false, allowPlan: false, allowMerge: false };
47
+ const gates = isValidApprovalGates(approvalGates)
48
+ ? normalizeApprovalGates(approvalGates)
49
+ : defaultGates;
24
50
  try {
25
- const result = await createFeature({ userInput, repositoryPath });
51
+ const result = await createFeature({ userInput, repositoryPath, approvalGates: gates });
26
52
  return NextResponse.json(result);
27
53
  }
28
54
  catch (error) {
@@ -1 +1 @@
1
- {"version":3,"file":"page.d.ts","sourceRoot":"","sources":["../../../../../src/presentation/web/app/page.tsx"],"names":[],"mappings":"AAQA,6EAA6E;AAC7E,eAAO,MAAM,OAAO,kBAAkB,CAAC;AAqBvC,wBAA8B,QAAQ,qDAsFrC"}
1
+ {"version":3,"file":"page.d.ts","sourceRoot":"","sources":["../../../../../src/presentation/web/app/page.tsx"],"names":[],"mappings":"AAWA,6EAA6E;AAC7E,eAAO,MAAM,OAAO,kBAAkB,CAAC;AAqBvC,wBAA8B,QAAQ,qDAmFrC"}
@@ -1,7 +1,7 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { ControlCenter } from '../components/features/control-center/index.js';
3
3
  import { getAgentRun, getFeatures } from '../../../../packages/core/src/infrastructure/di/use-cases-bridge.js';
4
- import { deriveState } from './derive-state.js';
4
+ import { deriveNodeState, deriveProgress, } from '../components/common/feature-node/derive-feature-state.js';
5
5
  import { layoutWithDagre } from '../lib/layout-with-dagre.js';
6
6
  /** Force request-time rendering so the globalThis DI bridge is available. */
7
7
  export const dynamic = 'force-dynamic';
@@ -26,12 +26,7 @@ export default async function HomePage() {
26
26
  const features = await getFeatures();
27
27
  const featuresWithRuns = await Promise.all(features.map(async (feature) => {
28
28
  const run = feature.agentRunId ? await getAgentRun(feature.agentRunId) : null;
29
- return {
30
- feature,
31
- agentStatus: run?.status,
32
- agentError: run?.error,
33
- agentResult: run?.result,
34
- };
29
+ return { feature, run };
35
30
  }));
36
31
  // Group features by repository path
37
32
  const featuresByRepo = {};
@@ -53,9 +48,9 @@ export default async function HomePage() {
53
48
  position: { x: 0, y: 0 },
54
49
  data: { name: repoName },
55
50
  });
56
- repoFeatures.forEach(({ feature, agentStatus, agentError, agentResult }) => {
57
- const agentNode = agentResult?.startsWith('node:') ? agentResult.slice(5) : undefined;
58
- const lifecycle = agentStatus === 'completed'
51
+ repoFeatures.forEach(({ feature, run }) => {
52
+ const agentNode = run?.result?.startsWith('node:') ? run.result.slice(5) : undefined;
53
+ const lifecycle = run?.status === 'completed'
59
54
  ? 'maintain'
60
55
  : ((agentNode ? nodeToLifecyclePhase[agentNode] : undefined) ??
61
56
  lifecycleMap[feature.lifecycle] ??
@@ -67,8 +62,10 @@ export default async function HomePage() {
67
62
  lifecycle,
68
63
  repositoryPath: feature.repositoryPath,
69
64
  branch: feature.branch,
70
- ...deriveState(lifecycle, agentStatus),
71
- ...(agentError && { errorMessage: agentError }),
65
+ state: deriveNodeState(feature, run),
66
+ progress: deriveProgress(feature),
67
+ ...(run?.agentType && { agentType: run.agentType }),
68
+ ...(run?.error && { errorMessage: run.error }),
72
69
  };
73
70
  const featureNodeId = `feat-${feature.id}`;
74
71
  nodes.push({
@@ -1,15 +1,11 @@
1
- import type { FileAttachment } from '../../../../../../packages/core/src/infrastructure/services/file-dialog.service.js';
1
+ import type { CreateFeatureInput } from '../../../../../../packages/core/src/infrastructure/di/use-cases-bridge.js';
2
2
  export type { FileAttachment } from '../../../../../../packages/core/src/infrastructure/services/file-dialog.service.js';
3
- export interface CreateFeatureFormData {
4
- name: string;
5
- description: string;
6
- attachments: FileAttachment[];
7
- }
8
3
  export interface FeatureCreateDrawerProps {
9
4
  open: boolean;
10
5
  onClose: () => void;
11
- onSubmit: (data: CreateFeatureFormData) => void;
6
+ onSubmit: (data: CreateFeatureInput) => void;
7
+ repositoryPath: string;
12
8
  isSubmitting?: boolean;
13
9
  }
14
- export declare function FeatureCreateDrawer({ open, onClose, onSubmit, isSubmitting, }: FeatureCreateDrawerProps): import("react/jsx-runtime").JSX.Element;
10
+ export declare function FeatureCreateDrawer({ open, onClose, onSubmit, repositoryPath, isSubmitting, }: FeatureCreateDrawerProps): import("react/jsx-runtime").JSX.Element;
15
11
  //# sourceMappingURL=feature-create-drawer.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"feature-create-drawer.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/components/common/feature-create-drawer/feature-create-drawer.tsx"],"names":[],"mappings":"AA4BA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,0DAA0D,CAAC;AAG/F,YAAY,EAAE,cAAc,EAAE,MAAM,0DAA0D,CAAC;AAE/F,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,cAAc,EAAE,CAAC;CAC/B;AAED,MAAM,WAAW,wBAAwB;IACvC,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,QAAQ,EAAE,CAAC,IAAI,EAAE,qBAAqB,KAAK,IAAI,CAAC;IAChD,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AAED,wBAAgB,mBAAmB,CAAC,EAClC,IAAI,EACJ,OAAO,EACP,QAAQ,EACR,YAAoB,GACrB,EAAE,wBAAwB,2CAkK1B"}
1
+ {"version":3,"file":"feature-create-drawer.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/components/common/feature-create-drawer/feature-create-drawer.tsx"],"names":[],"mappings":"AA8BA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,iDAAiD,CAAC;AAG1F,YAAY,EAAE,cAAc,EAAE,MAAM,0DAA0D,CAAC;AAiC/F,MAAM,WAAW,wBAAwB;IACvC,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,QAAQ,EAAE,CAAC,IAAI,EAAE,kBAAkB,KAAK,IAAI,CAAC;IAC7C,cAAc,EAAE,MAAM,CAAC;IACvB,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AAED,wBAAgB,mBAAmB,CAAC,EAClC,IAAI,EACJ,OAAO,EACP,QAAQ,EACR,cAAc,EACd,YAAoB,GACrB,EAAE,wBAAwB,2CA6L1B"}
@@ -8,17 +8,41 @@ import { Button } from '../../ui/button.js';
8
8
  import { Input } from '../../ui/input.js';
9
9
  import { Textarea } from '../../ui/textarea.js';
10
10
  import { Label } from '../../ui/label.js';
11
+ import { CheckboxGroup } from '../../ui/checkbox-group.js';
11
12
  import { Separator } from '../../ui/separator.js';
12
13
  import { Badge } from '../../ui/badge.js';
13
14
  import { pickFiles } from './pick-files.js';
14
- export function FeatureCreateDrawer({ open, onClose, onSubmit, isSubmitting = false, }) {
15
+ const AUTO_APPROVE_OPTIONS = [
16
+ { id: 'allowPrd', label: 'PRD', description: 'Auto-approve requirements move to planning.' },
17
+ { id: 'allowPlan', label: 'Plan', description: 'Auto-approve planning move to implementation.' },
18
+ { id: 'allowMerge', label: 'Merge', description: 'Auto-approve merge move to Done.' },
19
+ ];
20
+ const EMPTY_GATES = {
21
+ allowPrd: false,
22
+ allowPlan: false,
23
+ allowMerge: false,
24
+ };
25
+ function composeUserInput(name, description, attachments) {
26
+ let userInput = `Feature: ${name}`;
27
+ if (description) {
28
+ userInput += `\n\n${description}`;
29
+ }
30
+ if (attachments.length > 0) {
31
+ const paths = attachments.map((a) => `- ${a.path}`).join('\n');
32
+ userInput += `\n\nAttached files:\n${paths}`;
33
+ }
34
+ return userInput;
35
+ }
36
+ export function FeatureCreateDrawer({ open, onClose, onSubmit, repositoryPath, isSubmitting = false, }) {
15
37
  const [name, setName] = useState('');
16
38
  const [description, setDescription] = useState('');
17
39
  const [attachments, setAttachments] = useState([]);
40
+ const [approvalGates, setApprovalGates] = useState({ ...EMPTY_GATES });
18
41
  const resetForm = useCallback(() => {
19
42
  setName('');
20
43
  setDescription('');
21
44
  setAttachments([]);
45
+ setApprovalGates({ ...EMPTY_GATES });
22
46
  }, []);
23
47
  const handleOpenChange = useCallback((nextOpen) => {
24
48
  if (!nextOpen) {
@@ -30,8 +54,17 @@ export function FeatureCreateDrawer({ open, onClose, onSubmit, isSubmitting = fa
30
54
  e.preventDefault();
31
55
  if (!name.trim())
32
56
  return;
33
- onSubmit({ name: name.trim(), description: description.trim(), attachments });
34
- }, [name, description, attachments, onSubmit]);
57
+ const userInput = composeUserInput(name.trim(), description.trim() || undefined, attachments);
58
+ onSubmit({
59
+ userInput,
60
+ repositoryPath,
61
+ approvalGates: {
62
+ allowPrd: approvalGates.allowPrd ?? false,
63
+ allowPlan: approvalGates.allowPlan ?? false,
64
+ allowMerge: approvalGates.allowMerge ?? false,
65
+ },
66
+ });
67
+ }, [name, description, attachments, approvalGates, repositoryPath, onSubmit]);
35
68
  const handleAddFiles = useCallback(async () => {
36
69
  try {
37
70
  const files = await pickFiles();
@@ -46,7 +79,7 @@ export function FeatureCreateDrawer({ open, onClose, onSubmit, isSubmitting = fa
46
79
  const handleRemoveFile = useCallback((path) => {
47
80
  setAttachments((prev) => prev.filter((f) => f.path !== path));
48
81
  }, []);
49
- return (_jsx(Drawer, { direction: "right", modal: false, open: open, onOpenChange: handleOpenChange, children: _jsxs(DrawerContent, { direction: "right", className: "w-96", showCloseButton: false, children: [_jsx("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", children: _jsx(XIcon, { className: "size-4" }) }), _jsxs(DrawerHeader, { children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "h-2.5 w-2.5 shrink-0 rounded-full bg-blue-500" }), _jsx(DrawerTitle, { children: "NEW FEATURE" })] }), _jsx(DrawerDescription, { asChild: true, children: _jsx("div", { children: _jsx(Badge, { variant: "secondary", children: "Creating..." }) }) })] }), _jsx(Separator, {}), _jsx("div", { className: "flex-1 overflow-y-auto p-4", children: _jsxs("form", { id: "create-feature-form", onSubmit: handleSubmit, className: "flex flex-col gap-4", children: [_jsxs("div", { className: "flex flex-col gap-1.5", children: [_jsx(Label, { htmlFor: "feature-name", className: "text-muted-foreground text-xs font-semibold tracking-wider", children: "FEATURE NAME" }), _jsx(Input, { id: "feature-name", placeholder: "e.g. GitHub OAuth Login", value: name, onChange: (e) => setName(e.target.value), required: true, disabled: isSubmitting })] }), _jsxs("div", { className: "flex flex-col gap-1.5", children: [_jsx(Label, { htmlFor: "feature-description", className: "text-muted-foreground text-xs font-semibold tracking-wider", children: "DESCRIPTION" }), _jsx(Textarea, { id: "feature-description", placeholder: "Describe what this feature does...", value: description, onChange: (e) => setDescription(e.target.value), rows: 4, disabled: isSubmitting })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsxs(Label, { className: "text-muted-foreground text-xs font-semibold tracking-wider", children: ["ATTACHMENTS", attachments.length > 0 && (_jsxs("span", { className: "text-muted-foreground/60 ml-1.5", children: ["(", attachments.length, ")"] }))] }), _jsxs(Button, { type: "button", variant: "outline", size: "xs", onClick: handleAddFiles, disabled: isSubmitting, children: [_jsx(PlusIcon, { className: "size-3" }), "Add Files"] })] }), attachments.length > 0 && (_jsx("div", { className: "flex flex-col gap-2", children: attachments.map((file) => (_jsx(AttachmentCard, { file: file, onRemove: () => handleRemoveFile(file.path), disabled: isSubmitting }, file.path))) }))] })] }) }), _jsx(Separator, {}), _jsxs(DrawerFooter, { className: "flex-row justify-end gap-2", children: [_jsx(Button, { variant: "outline", onClick: onClose, disabled: isSubmitting, children: "Cancel" }), _jsx(Button, { type: "submit", form: "create-feature-form", disabled: !name.trim() || isSubmitting, children: isSubmitting ? 'Creating...' : '+ Create Feature' })] })] }) }));
82
+ return (_jsx(Drawer, { direction: "right", modal: false, open: open, onOpenChange: handleOpenChange, children: _jsxs(DrawerContent, { direction: "right", className: "w-96", showCloseButton: false, children: [_jsx("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", children: _jsx(XIcon, { className: "size-4" }) }), _jsxs(DrawerHeader, { children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "h-2.5 w-2.5 shrink-0 rounded-full bg-blue-500" }), _jsx(DrawerTitle, { children: "NEW FEATURE" })] }), _jsx(DrawerDescription, { asChild: true, children: _jsx("div", { children: _jsx(Badge, { variant: "secondary", children: "Creating..." }) }) })] }), _jsx(Separator, {}), _jsx("div", { className: "flex-1 overflow-y-auto p-4", children: _jsxs("form", { id: "create-feature-form", onSubmit: handleSubmit, className: "flex flex-col gap-4", children: [_jsxs("div", { className: "flex flex-col gap-1.5", children: [_jsx(Label, { htmlFor: "feature-name", className: "text-muted-foreground text-xs font-semibold tracking-wider", children: "FEATURE NAME" }), _jsx(Input, { id: "feature-name", placeholder: "e.g. GitHub OAuth Login", value: name, onChange: (e) => setName(e.target.value), required: true, disabled: isSubmitting })] }), _jsxs("div", { className: "flex flex-col gap-1.5", children: [_jsx(Label, { htmlFor: "feature-description", className: "text-muted-foreground text-xs font-semibold tracking-wider", children: "DESCRIPTION" }), _jsx(Textarea, { id: "feature-description", placeholder: "Describe what this feature does...", value: description, onChange: (e) => setDescription(e.target.value), rows: 4, disabled: isSubmitting })] }), _jsxs("div", { className: "flex flex-col gap-1.5", children: [_jsx(Label, { className: "text-muted-foreground text-xs font-semibold tracking-wider", children: "APPROVE" }), _jsx(CheckboxGroup, { label: "Autonomous Mode", description: "YOLO!", parentAriaLabel: "Auto approve all", options: AUTO_APPROVE_OPTIONS, value: approvalGates, onValueChange: setApprovalGates, disabled: isSubmitting })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsxs(Label, { className: "text-muted-foreground text-xs font-semibold tracking-wider", children: ["ATTACHMENTS", attachments.length > 0 && (_jsxs("span", { className: "text-muted-foreground/60 ml-1.5", children: ["(", attachments.length, ")"] }))] }), _jsxs(Button, { type: "button", variant: "outline", size: "xs", onClick: handleAddFiles, disabled: isSubmitting, children: [_jsx(PlusIcon, { className: "size-3" }), "Add Files"] })] }), attachments.length > 0 && (_jsx("div", { className: "flex flex-col gap-2", children: attachments.map((file) => (_jsx(AttachmentCard, { file: file, onRemove: () => handleRemoveFile(file.path), disabled: isSubmitting }, file.path))) }))] })] }) }), _jsx(Separator, {}), _jsxs(DrawerFooter, { className: "flex-row justify-end gap-2", children: [_jsx(Button, { variant: "outline", onClick: onClose, disabled: isSubmitting, children: "Cancel" }), _jsx(Button, { type: "submit", form: "create-feature-form", disabled: !name.trim() || isSubmitting, children: isSubmitting ? 'Creating...' : '+ Create Feature' })] })] }) }));
50
83
  }
51
84
  /* ---------------------------------------------------------------------------
52
85
  * Private sub-components & utilities
@@ -7,6 +7,8 @@ import { FeatureCreateDrawer } from './feature-create-drawer.js';
7
7
  * ### Form sections
8
8
  * - **Feature Name** (required) — text input, submit is disabled when empty
9
9
  * - **Description** (optional) — multi-line textarea
10
+ * - **Auto Approve** — Tri-state parent checkbox with 3 child checkboxes (PRD, Plan, Merge).
11
+ * Parent shows indeterminate when some children are selected, checks/unchecks all on click.
10
12
  * - **Attachments** (optional) — native OS file picker with extension-based icon system:
11
13
  * image → blue, PDF → red, code/text → emerald, generic → gray.
12
14
  * Each attachment card shows the **full absolute file path**.
@@ -16,12 +18,14 @@ import { FeatureCreateDrawer } from './feature-create-drawer.js';
16
18
  * |------|------|-------------|
17
19
  * | `open` | `boolean` | Controls drawer visibility |
18
20
  * | `onClose` | `() => void` | Called on dismiss (close button, cancel, or backdrop) |
19
- * | `onSubmit` | `(data: CreateFeatureFormData) => void` | Called with `{ name, description, attachments }` |
21
+ * | `onSubmit` | `(data: CreateFeatureInput) => void` | Called with `{ userInput, repositoryPath, approvalGates }` |
22
+ * | `repositoryPath` | `string` | Repository path (mandatory) included in the submitted data |
20
23
  * | `isSubmitting` | `boolean` | Disables all fields and shows "Creating..." on submit button |
21
24
  *
22
25
  * ### Behavior
23
- * - Form resets (name, description, attachments cleared) when the drawer closes
26
+ * - Form resets (name, description, attachments, checkboxes) when the drawer closes
24
27
  * - Submit button is disabled when name is empty OR `isSubmitting` is true
28
+ * - `approvalGates` always included: `{ allowPrd, allowPlan, allowMerge }` (all false by default)
25
29
  * - Non-modal (`modal={false}`) — canvas stays interactive behind the drawer
26
30
  * - Fixed width: 384px (`w-96`)
27
31
  * - Attachments use native OS file picker via `pickFiles()` — returns `FileAttachment[]`
@@ -50,11 +54,31 @@ export declare const ValidationDisabled: Story;
50
54
  /** Click to open the drawer for quick visual inspection. */
51
55
  export declare const PreOpened: Story;
52
56
  /**
53
- * Fully interactive storyopen the drawer, fill the form, and click
54
- * "Add Files" to attach files via the native OS file picker.
57
+ * All checkboxes unchecked (default) drawer opens with no auto-approve gates enabled.
58
+ * This is the safest default: every phase requires manual review.
59
+ */
60
+ export declare const AllUnchecked: Story;
61
+ /**
62
+ * All checkboxes checked via parent "Auto Approve" — fully autonomous mode.
63
+ * Clicking the parent checkbox selects all children at once.
64
+ */
65
+ export declare const AllChecked: Story;
66
+ /**
67
+ * Only PRD checkbox checked — parent shows **indeterminate** (dash) state,
68
+ * indicating partial selection at a glance.
69
+ */
70
+ export declare const PrdOnly: Story;
71
+ /**
72
+ * Only Merge checkbox checked — parent shows **indeterminate** (dash) state.
73
+ * Manual review for requirements and planning, auto-approve merge to Done.
74
+ */
75
+ export declare const MergeOnly: Story;
76
+ /**
77
+ * Fully interactive story — open the drawer, fill the form, toggle checkboxes,
78
+ * and click "Add Files" to attach files via the native OS file picker.
55
79
  *
56
- * **Submitted data** is displayed in a styled panel showing `FileAttachment[]`
57
- * with **full absolute file paths**, filenames, and sizes.
80
+ * **Submitted data** is displayed in a styled panel showing `CreateFeatureInput`
81
+ * with `approvalGates: { allowPrd, allowPlan, allowMerge }`.
58
82
  *
59
83
  * In Storybook, the native picker won't work (no backend), but submitted data
60
84
  * would show the paths if files were attached programmatically.
@@ -1 +1 @@
1
- {"version":3,"file":"feature-create-drawer.stories.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/components/common/feature-create-drawer/feature-create-drawer.stories.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAEvD,OAAO,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAQ9D;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,QAAA,MAAM,IAAI,EAAE,IAAI,CAAC,OAAO,mBAAmB,CA0B1C,CAAC;AAEF,eAAe,IAAI,CAAC;AACpB,KAAK,KAAK,GAAG,QAAQ,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAgDlD,uFAAuF;AACvF,eAAO,MAAM,OAAO,EAAE,KAErB,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,SAAS,EAAE,KAiBvB,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,UAAU,EAAE,KAExB,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,kBAAkB,EAAE,KAMhC,CAAC;AAEF,4DAA4D;AAC5D,eAAO,MAAM,SAAS,EAAE,KAMvB,CAAC;AAMF;;;;;;;;;GASG;AACH,eAAO,MAAM,WAAW,EAAE,KAiDzB,CAAC"}
1
+ {"version":3,"file":"feature-create-drawer.stories.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/components/common/feature-create-drawer/feature-create-drawer.stories.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAEvD,OAAO,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAQ9D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,QAAA,MAAM,IAAI,EAAE,IAAI,CAAC,OAAO,mBAAmB,CA8B1C,CAAC;AAEF,eAAe,IAAI,CAAC;AACpB,KAAK,KAAK,GAAG,QAAQ,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAiDlD,uFAAuF;AACvF,eAAO,MAAM,OAAO,EAAE,KAErB,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,SAAS,EAAE,KAiBvB,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,UAAU,EAAE,KAExB,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,kBAAkB,EAAE,KAMhC,CAAC;AAEF,4DAA4D;AAC5D,eAAO,MAAM,SAAS,EAAE,KAMvB,CAAC;AAMF;;;GAGG;AACH,eAAO,MAAM,YAAY,EAAE,KAM1B,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,UAAU,EAAE,KAUxB,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,OAAO,EAAE,KAUrB,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,SAAS,EAAE,KAUvB,CAAC;AAMF;;;;;;;;;GASG;AACH,eAAO,MAAM,WAAW,EAAE,KAsCzB,CAAC"}
@@ -13,6 +13,8 @@ import { Button } from '../../ui/button.js';
13
13
  * ### Form sections
14
14
  * - **Feature Name** (required) — text input, submit is disabled when empty
15
15
  * - **Description** (optional) — multi-line textarea
16
+ * - **Auto Approve** — Tri-state parent checkbox with 3 child checkboxes (PRD, Plan, Merge).
17
+ * Parent shows indeterminate when some children are selected, checks/unchecks all on click.
16
18
  * - **Attachments** (optional) — native OS file picker with extension-based icon system:
17
19
  * image → blue, PDF → red, code/text → emerald, generic → gray.
18
20
  * Each attachment card shows the **full absolute file path**.
@@ -22,12 +24,14 @@ import { Button } from '../../ui/button.js';
22
24
  * |------|------|-------------|
23
25
  * | `open` | `boolean` | Controls drawer visibility |
24
26
  * | `onClose` | `() => void` | Called on dismiss (close button, cancel, or backdrop) |
25
- * | `onSubmit` | `(data: CreateFeatureFormData) => void` | Called with `{ name, description, attachments }` |
27
+ * | `onSubmit` | `(data: CreateFeatureInput) => void` | Called with `{ userInput, repositoryPath, approvalGates }` |
28
+ * | `repositoryPath` | `string` | Repository path (mandatory) included in the submitted data |
26
29
  * | `isSubmitting` | `boolean` | Disables all fields and shows "Creating..." on submit button |
27
30
  *
28
31
  * ### Behavior
29
- * - Form resets (name, description, attachments cleared) when the drawer closes
32
+ * - Form resets (name, description, attachments, checkboxes) when the drawer closes
30
33
  * - Submit button is disabled when name is empty OR `isSubmitting` is true
34
+ * - `approvalGates` always included: `{ allowPrd, allowPlan, allowMerge }` (all false by default)
31
35
  * - Non-modal (`modal={false}`) — canvas stays interactive behind the drawer
32
36
  * - Fixed width: 384px (`w-96`)
33
37
  * - Attachments use native OS file picker via `pickFiles()` — returns `FileAttachment[]`
@@ -49,7 +53,11 @@ const meta = {
49
53
  description: 'Callback fired when the drawer is dismissed (close button, cancel, or backdrop click).',
50
54
  },
51
55
  onSubmit: {
52
- description: 'Callback fired with `CreateFeatureFormData` when the form is submitted. Receives `{ name, description, attachments }`.',
56
+ description: 'Callback fired with `CreateFeatureInput` when the form is submitted. Receives `{ userInput, repositoryPath, approvalGates }`.',
57
+ },
58
+ repositoryPath: {
59
+ control: 'text',
60
+ description: 'Repository path (mandatory) included in the submitted data.',
53
61
  },
54
62
  isSubmitting: {
55
63
  control: 'boolean',
@@ -75,7 +83,7 @@ function CreateDrawerTrigger({ isSubmitting = false, label = 'Open Create Featur
75
83
  }, onSubmit: (data) => {
76
84
  logSubmit(data);
77
85
  setOpen(false);
78
- }, isSubmitting: isSubmitting })] }));
86
+ }, repositoryPath: "/Users/dev/my-repo", isSubmitting: isSubmitting })] }));
79
87
  }
80
88
  /* ---------------------------------------------------------------------------
81
89
  * Per-state stories
@@ -127,15 +135,71 @@ export const PreOpened = {
127
135
  await userEvent.click(canvas.getByRole('button', { name: 'Open Drawer' }));
128
136
  },
129
137
  };
138
+ /* ---------------------------------------------------------------------------
139
+ * Auto-approve checkbox stories
140
+ * ------------------------------------------------------------------------- */
141
+ /**
142
+ * All checkboxes unchecked (default) — drawer opens with no auto-approve gates enabled.
143
+ * This is the safest default: every phase requires manual review.
144
+ */
145
+ export const AllUnchecked = {
146
+ render: () => _jsx(CreateDrawerTrigger, { label: "Open (All Unchecked)" }),
147
+ play: async ({ canvasElement }) => {
148
+ const canvas = within(canvasElement);
149
+ await userEvent.click(canvas.getByRole('button', { name: 'Open (All Unchecked)' }));
150
+ },
151
+ };
152
+ /**
153
+ * All checkboxes checked via parent "Auto Approve" — fully autonomous mode.
154
+ * Clicking the parent checkbox selects all children at once.
155
+ */
156
+ export const AllChecked = {
157
+ render: () => _jsx(CreateDrawerTrigger, { label: "Open (All Checked)" }),
158
+ play: async ({ canvasElement }) => {
159
+ const canvas = within(canvasElement);
160
+ await userEvent.click(canvas.getByRole('button', { name: 'Open (All Checked)' }));
161
+ const body = within(canvasElement.ownerDocument.body);
162
+ const parent = await body.findByLabelText('Auto approve all');
163
+ await userEvent.click(parent);
164
+ },
165
+ };
166
+ /**
167
+ * Only PRD checkbox checked — parent shows **indeterminate** (dash) state,
168
+ * indicating partial selection at a glance.
169
+ */
170
+ export const PrdOnly = {
171
+ render: () => _jsx(CreateDrawerTrigger, { label: "Open (PRD Only)" }),
172
+ play: async ({ canvasElement }) => {
173
+ const canvas = within(canvasElement);
174
+ await userEvent.click(canvas.getByRole('button', { name: 'Open (PRD Only)' }));
175
+ const body = within(canvasElement.ownerDocument.body);
176
+ const prd = await body.findByLabelText('PRD');
177
+ await userEvent.click(prd);
178
+ },
179
+ };
180
+ /**
181
+ * Only Merge checkbox checked — parent shows **indeterminate** (dash) state.
182
+ * Manual review for requirements and planning, auto-approve merge to Done.
183
+ */
184
+ export const MergeOnly = {
185
+ render: () => _jsx(CreateDrawerTrigger, { label: "Open (Merge Only)" }),
186
+ play: async ({ canvasElement }) => {
187
+ const canvas = within(canvasElement);
188
+ await userEvent.click(canvas.getByRole('button', { name: 'Open (Merge Only)' }));
189
+ const body = within(canvasElement.ownerDocument.body);
190
+ const merge = await body.findByLabelText('Merge');
191
+ await userEvent.click(merge);
192
+ },
193
+ };
130
194
  /* ---------------------------------------------------------------------------
131
195
  * Interactive story — full paths shown in submitted data panel
132
196
  * ------------------------------------------------------------------------- */
133
197
  /**
134
- * Fully interactive story — open the drawer, fill the form, and click
135
- * "Add Files" to attach files via the native OS file picker.
198
+ * Fully interactive story — open the drawer, fill the form, toggle checkboxes,
199
+ * and click "Add Files" to attach files via the native OS file picker.
136
200
  *
137
- * **Submitted data** is displayed in a styled panel showing `FileAttachment[]`
138
- * with **full absolute file paths**, filenames, and sizes.
201
+ * **Submitted data** is displayed in a styled panel showing `CreateFeatureInput`
202
+ * with `approvalGates: { allowPrd, allowPlan, allowMerge }`.
139
203
  *
140
204
  * In Storybook, the native picker won't work (no backend), but submitted data
141
205
  * would show the paths if files were attached programmatically.
@@ -144,21 +208,13 @@ export const Interactive = {
144
208
  render: function InteractiveRender() {
145
209
  const [open, setOpen] = useState(false);
146
210
  const [submitted, setSubmitted] = useState(null);
147
- return (_jsxs("div", { className: "flex h-screen items-start gap-4 p-4", children: [_jsxs("div", { className: "flex flex-col gap-3", children: [_jsx(Button, { variant: "outline", onClick: () => setOpen(true), children: "Open Create Feature" }), submitted ? (_jsxs("div", { className: "bg-muted/50 max-w-sm rounded-md border p-3", children: [_jsx("span", { className: "text-muted-foreground mb-1 text-xs font-semibold tracking-wider uppercase", children: "Last submitted" }), _jsx("pre", { className: "mt-1 text-xs whitespace-pre-wrap", children: JSON.stringify({
148
- name: submitted.name,
149
- description: submitted.description,
150
- attachments: submitted.attachments.map((f) => ({
151
- path: f.path,
152
- name: f.name,
153
- size: f.size,
154
- })),
155
- }, null, 2) })] })) : null] }), _jsx(FeatureCreateDrawer, { open: open, onClose: () => {
211
+ return (_jsxs("div", { className: "flex h-screen items-start gap-4 p-4", children: [_jsxs("div", { className: "flex flex-col gap-3", children: [_jsx(Button, { variant: "outline", onClick: () => setOpen(true), children: "Open Create Feature" }), submitted ? (_jsxs("div", { className: "bg-muted/50 max-w-sm rounded-md border p-3", children: [_jsx("span", { className: "text-muted-foreground mb-1 text-xs font-semibold tracking-wider uppercase", children: "Last submitted" }), _jsx("pre", { className: "mt-1 text-xs whitespace-pre-wrap", children: JSON.stringify(submitted, null, 2) })] })) : null] }), _jsx(FeatureCreateDrawer, { open: open, onClose: () => {
156
212
  setOpen(false);
157
213
  logClose();
158
214
  }, onSubmit: (data) => {
159
215
  logSubmit(data);
160
216
  setSubmitted(data);
161
217
  setOpen(false);
162
- } })] }));
218
+ }, repositoryPath: "/Users/dev/my-repo" })] }));
163
219
  },
164
220
  };
@@ -1,2 +1,2 @@
1
- export { FeatureCreateDrawer, type FeatureCreateDrawerProps, type CreateFeatureFormData, type FileAttachment, } from './feature-create-drawer.js';
1
+ export { FeatureCreateDrawer, type FeatureCreateDrawerProps, type FileAttachment, } from './feature-create-drawer.js';
2
2
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/components/common/feature-create-drawer/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,mBAAmB,EACnB,KAAK,wBAAwB,EAC7B,KAAK,qBAAqB,EAC1B,KAAK,cAAc,GACpB,MAAM,yBAAyB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/components/common/feature-create-drawer/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,mBAAmB,EACnB,KAAK,wBAAwB,EAC7B,KAAK,cAAc,GACpB,MAAM,yBAAyB,CAAC"}
@@ -20,10 +20,10 @@ function StateBadge({ data }) {
20
20
  return (_jsxs("div", { className: cn('flex items-center gap-2 rounded-full px-3 py-1.5 text-sm font-medium', config.badgeBgClass, config.badgeClass), children: [data.state === 'running' ? (_jsx(CometSpinner, { size: "sm", className: "shrink-0" })) : (_jsx(Icon, { className: "h-4 w-4 shrink-0" })), _jsx("span", { children: config.label })] }));
21
21
  }
22
22
  function DetailsSection({ data }) {
23
- const hasAnyDetail = data.description ?? data.agentName ?? data.runtime ?? data.blockedBy ?? data.errorMessage;
23
+ const hasAnyDetail = data.description ?? data.agentType ?? data.runtime ?? data.blockedBy ?? data.errorMessage;
24
24
  if (!hasAnyDetail)
25
25
  return null;
26
- return (_jsxs("div", { "data-testid": "feature-drawer-details", className: "flex flex-col gap-3 p-4", children: [data.description ? _jsx(DetailRow, { label: "Description", value: data.description }) : null, data.agentName ? _jsx(DetailRow, { label: "Agent", value: data.agentName }) : null, data.runtime ? _jsx(DetailRow, { label: "Runtime", value: data.runtime }) : null, data.blockedBy ? _jsx(DetailRow, { label: "Blocked by", value: data.blockedBy }) : null, data.errorMessage ? _jsx(DetailRow, { label: "Error", value: data.errorMessage }) : null] }));
26
+ return (_jsxs("div", { "data-testid": "feature-drawer-details", className: "flex flex-col gap-3 p-4", children: [data.description ? _jsx(DetailRow, { label: "Description", value: data.description }) : null, data.agentType ? _jsx(DetailRow, { label: "Agent", value: data.agentType }) : null, data.runtime ? _jsx(DetailRow, { label: "Runtime", value: data.runtime }) : null, data.blockedBy ? _jsx(DetailRow, { label: "Blocked by", value: data.blockedBy }) : null, data.errorMessage ? _jsx(DetailRow, { label: "Error", value: data.errorMessage }) : null] }));
27
27
  }
28
28
  function DetailRow({ label, value }) {
29
29
  return (_jsxs("div", { className: "flex flex-col gap-0.5", children: [_jsx("span", { className: "text-muted-foreground text-xs font-medium", children: label }), _jsx("span", { className: "text-sm", children: value })] }));
@@ -30,7 +30,7 @@ const runningData = {
30
30
  lifecycle: 'implementation',
31
31
  state: 'running',
32
32
  progress: 45,
33
- agentName: 'Planner',
33
+ agentType: 'claude-code',
34
34
  repositoryPath: '/home/user/my-repo',
35
35
  branch: 'feat/auth-module',
36
36
  };
@@ -118,7 +118,7 @@ export const AllFields = {
118
118
  lifecycle: 'implementation',
119
119
  state: 'running',
120
120
  progress: 68,
121
- agentName: 'Implementer',
121
+ agentType: 'cursor',
122
122
  runtime: '3h 15m',
123
123
  blockedBy: 'Database Migration',
124
124
  errorMessage: 'Token refresh failed: invalid_grant',
@@ -169,7 +169,7 @@ function AllLifecyclesRender() {
169
169
  lifecycle: phase,
170
170
  state: 'running',
171
171
  progress: 50,
172
- agentName: 'Agent',
172
+ agentType: 'claude-code',
173
173
  repositoryPath: '/home/user/my-repo',
174
174
  branch: 'feat/feature-name',
175
175
  });
@@ -1,9 +1,12 @@
1
1
  import { type LucideIcon } from 'lucide-react';
2
2
  import type { Node } from '@xyflow/react';
3
+ import type { AgentTypeValue } from './agent-type-icons.js';
3
4
  export type FeatureNodeState = 'running' | 'action-required' | 'done' | 'blocked' | 'error';
4
5
  export type FeatureLifecyclePhase = 'requirements' | 'research' | 'implementation' | 'review' | 'deploy' | 'maintain';
5
6
  /** Human-readable display labels for lifecycle phases. */
6
7
  export declare const lifecycleDisplayLabels: Record<FeatureLifecyclePhase, string>;
8
+ /** Present-participle verbs for the running badge, keyed by lifecycle phase. */
9
+ export declare const lifecycleRunningVerbs: Record<FeatureLifecyclePhase, string>;
7
10
  export interface FeatureNodeData {
8
11
  [key: string]: unknown;
9
12
  name: string;
@@ -22,8 +25,8 @@ export interface FeatureNodeData {
22
25
  blockedBy?: string;
23
26
  /** Short error message for error state */
24
27
  errorMessage?: string;
25
- /** Agent name shown in the running badge (e.g. "Planner") */
26
- agentName?: string;
28
+ /** Agent executor type (e.g. "claude-code", "cursor"). */
29
+ agentType?: AgentTypeValue;
27
30
  onAction?: () => void;
28
31
  onSettings?: () => void;
29
32
  showHandles?: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"feature-node-state-config.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/components/common/feature-node/feature-node-state-config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAmD,KAAK,UAAU,EAAE,MAAM,cAAc,CAAC;AAChG,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;AAE1C,MAAM,MAAM,gBAAgB,GAAG,SAAS,GAAG,iBAAiB,GAAG,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC;AAE5F,MAAM,MAAM,qBAAqB,GAC7B,cAAc,GACd,UAAU,GACV,gBAAgB,GAChB,QAAQ,GACR,QAAQ,GACR,UAAU,CAAC;AAEf,0DAA0D;AAC1D,eAAO,MAAM,sBAAsB,EAAE,MAAM,CAAC,qBAAqB,EAAE,MAAM,CAOxE,CAAC;AAEF,MAAM,WAAW,eAAe;IAC9B,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,qBAAqB,CAAC;IACjC,KAAK,EAAE,gBAAgB,CAAC;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,8CAA8C;IAC9C,cAAc,EAAE,MAAM,CAAC;IACvB,uCAAuC;IACvC,MAAM,EAAE,MAAM,CAAC;IACf,4DAA4D;IAC5D,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,2CAA2C;IAC3C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,0CAA0C;IAC1C,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,6DAA6D;IAC7D,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC;IACxB,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,MAAM,MAAM,eAAe,GAAG,IAAI,CAAC,eAAe,EAAE,aAAa,CAAC,CAAC;AAEnE,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,UAAU,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,eAAe,EAAE,OAAO,CAAC;CAC1B;AAED,eAAO,MAAM,sBAAsB,EAAE,MAAM,CAAC,gBAAgB,EAAE,sBAAsB,CAmDnF,CAAC"}
1
+ {"version":3,"file":"feature-node-state-config.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/components/common/feature-node/feature-node-state-config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAmD,KAAK,UAAU,EAAE,MAAM,cAAc,CAAC;AAChG,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;AAC1C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAEzD,MAAM,MAAM,gBAAgB,GAAG,SAAS,GAAG,iBAAiB,GAAG,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC;AAE5F,MAAM,MAAM,qBAAqB,GAC7B,cAAc,GACd,UAAU,GACV,gBAAgB,GAChB,QAAQ,GACR,QAAQ,GACR,UAAU,CAAC;AAEf,0DAA0D;AAC1D,eAAO,MAAM,sBAAsB,EAAE,MAAM,CAAC,qBAAqB,EAAE,MAAM,CAOxE,CAAC;AAEF,gFAAgF;AAChF,eAAO,MAAM,qBAAqB,EAAE,MAAM,CAAC,qBAAqB,EAAE,MAAM,CAOvE,CAAC;AAEF,MAAM,WAAW,eAAe;IAC9B,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,qBAAqB,CAAC;IACjC,KAAK,EAAE,gBAAgB,CAAC;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,8CAA8C;IAC9C,cAAc,EAAE,MAAM,CAAC;IACvB,uCAAuC;IACvC,MAAM,EAAE,MAAM,CAAC;IACf,4DAA4D;IAC5D,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,2CAA2C;IAC3C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,0CAA0C;IAC1C,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,0DAA0D;IAC1D,SAAS,CAAC,EAAE,cAAc,CAAC;IAC3B,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC;IACxB,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,MAAM,MAAM,eAAe,GAAG,IAAI,CAAC,eAAe,EAAE,aAAa,CAAC,CAAC;AAEnE,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,UAAU,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,eAAe,EAAE,OAAO,CAAC;CAC1B;AAED,eAAO,MAAM,sBAAsB,EAAE,MAAM,CAAC,gBAAgB,EAAE,sBAAsB,CAmDnF,CAAC"}
@@ -8,6 +8,15 @@ export const lifecycleDisplayLabels = {
8
8
  deploy: 'DEPLOY & QA',
9
9
  maintain: 'COMPLETED',
10
10
  };
11
+ /** Present-participle verbs for the running badge, keyed by lifecycle phase. */
12
+ export const lifecycleRunningVerbs = {
13
+ requirements: 'Analyzing',
14
+ research: 'Researching',
15
+ implementation: 'Implementing',
16
+ review: 'Reviewing',
17
+ deploy: 'Deploying',
18
+ maintain: 'Maintaining',
19
+ };
11
20
  export const featureNodeStateConfig = {
12
21
  running: {
13
22
  icon: Loader2,