@assistkick/create 1.6.0 → 1.8.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 (214) hide show
  1. package/package.json +2 -2
  2. package/templates/assistkick-product-system/.env.example +1 -0
  3. package/templates/assistkick-product-system/local.db +0 -0
  4. package/templates/assistkick-product-system/package.json +4 -2
  5. package/templates/assistkick-product-system/packages/backend/package.json +2 -0
  6. package/templates/assistkick-product-system/packages/backend/src/routes/agents.ts +165 -0
  7. package/templates/assistkick-product-system/packages/backend/src/routes/files.test.ts +358 -0
  8. package/templates/assistkick-product-system/packages/backend/src/routes/files.ts +356 -0
  9. package/templates/assistkick-product-system/packages/backend/src/routes/git.ts +96 -1
  10. package/templates/assistkick-product-system/packages/backend/src/routes/graph.ts +1 -0
  11. package/templates/assistkick-product-system/packages/backend/src/routes/kanban.ts +43 -4
  12. package/templates/assistkick-product-system/packages/backend/src/routes/pipeline.ts +200 -84
  13. package/templates/assistkick-product-system/packages/backend/src/routes/projects.ts +6 -3
  14. package/templates/assistkick-product-system/packages/backend/src/routes/terminal.ts +53 -17
  15. package/templates/assistkick-product-system/packages/backend/src/routes/video.ts +218 -0
  16. package/templates/assistkick-product-system/packages/backend/src/routes/workflow_groups.ts +119 -0
  17. package/templates/assistkick-product-system/packages/backend/src/routes/workflows.ts +154 -0
  18. package/templates/assistkick-product-system/packages/backend/src/server.ts +81 -9
  19. package/templates/assistkick-product-system/packages/backend/src/services/agent_service.test.ts +489 -0
  20. package/templates/assistkick-product-system/packages/backend/src/services/agent_service.ts +416 -0
  21. package/templates/assistkick-product-system/packages/backend/src/services/bundle_service.test.ts +189 -0
  22. package/templates/assistkick-product-system/packages/backend/src/services/bundle_service.ts +182 -0
  23. package/templates/assistkick-product-system/packages/backend/src/services/init.ts +28 -78
  24. package/templates/assistkick-product-system/packages/backend/src/services/project_service.test.ts +16 -0
  25. package/templates/assistkick-product-system/packages/backend/src/services/project_service.ts +73 -2
  26. package/templates/assistkick-product-system/packages/backend/src/services/project_workspace_service.test.ts +4 -4
  27. package/templates/assistkick-product-system/packages/backend/src/services/project_workspace_service.ts +87 -11
  28. package/templates/assistkick-product-system/packages/backend/src/services/pty_session_manager.test.ts +210 -69
  29. package/templates/assistkick-product-system/packages/backend/src/services/pty_session_manager.ts +210 -215
  30. package/templates/assistkick-product-system/packages/backend/src/services/ssh_key_service.test.ts +162 -0
  31. package/templates/assistkick-product-system/packages/backend/src/services/ssh_key_service.ts +148 -0
  32. package/templates/assistkick-product-system/packages/backend/src/services/terminal_ws_handler.ts +11 -5
  33. package/templates/assistkick-product-system/packages/backend/src/services/tts_service.test.ts +64 -0
  34. package/templates/assistkick-product-system/packages/backend/src/services/tts_service.ts +134 -0
  35. package/templates/assistkick-product-system/packages/backend/src/services/video_render_service.test.ts +256 -0
  36. package/templates/assistkick-product-system/packages/backend/src/services/video_render_service.ts +258 -0
  37. package/templates/assistkick-product-system/packages/backend/src/services/workflow_group_service.ts +106 -0
  38. package/templates/assistkick-product-system/packages/backend/src/services/workflow_service.test.ts +275 -0
  39. package/templates/assistkick-product-system/packages/backend/src/services/workflow_service.ts +222 -0
  40. package/templates/assistkick-product-system/packages/frontend/index.html +3 -0
  41. package/templates/assistkick-product-system/packages/frontend/package-lock.json +800 -11
  42. package/templates/assistkick-product-system/packages/frontend/package.json +11 -1
  43. package/templates/assistkick-product-system/packages/frontend/src/App.tsx +24 -7
  44. package/templates/assistkick-product-system/packages/frontend/src/api/client.ts +456 -16
  45. package/templates/assistkick-product-system/packages/frontend/src/api/client_files.test.ts +172 -0
  46. package/templates/assistkick-product-system/packages/frontend/src/api/client_video.test.ts +238 -0
  47. package/templates/assistkick-product-system/packages/frontend/src/components/AgentsView.tsx +307 -0
  48. package/templates/assistkick-product-system/packages/frontend/src/components/CoherenceView.tsx +82 -66
  49. package/templates/assistkick-product-system/packages/frontend/src/components/CompositionPlaceholder.tsx +97 -0
  50. package/templates/assistkick-product-system/packages/frontend/src/components/DesignSystemView.tsx +383 -0
  51. package/templates/assistkick-product-system/packages/frontend/src/components/EditorTabBar.tsx +57 -0
  52. package/templates/assistkick-product-system/packages/frontend/src/components/FileTree.tsx +313 -0
  53. package/templates/assistkick-product-system/packages/frontend/src/components/FileTreeContextMenu.tsx +61 -0
  54. package/templates/assistkick-product-system/packages/frontend/src/components/FileTreeInlineInput.tsx +73 -0
  55. package/templates/assistkick-product-system/packages/frontend/src/components/FilesView.tsx +404 -0
  56. package/templates/assistkick-product-system/packages/frontend/src/components/GitRepoModal.tsx +193 -64
  57. package/templates/assistkick-product-system/packages/frontend/src/components/GraphLegend.tsx +71 -73
  58. package/templates/assistkick-product-system/packages/frontend/src/components/GraphSettings.tsx +8 -8
  59. package/templates/assistkick-product-system/packages/frontend/src/components/GraphView.tsx +1 -1
  60. package/templates/assistkick-product-system/packages/frontend/src/components/InviteUserDialog.tsx +15 -11
  61. package/templates/assistkick-product-system/packages/frontend/src/components/KanbanView.tsx +226 -291
  62. package/templates/assistkick-product-system/packages/frontend/src/components/LoginPage.tsx +14 -14
  63. package/templates/assistkick-product-system/packages/frontend/src/components/ProjectSelector.tsx +54 -33
  64. package/templates/assistkick-product-system/packages/frontend/src/components/QaIssueSheet.tsx +40 -66
  65. package/templates/assistkick-product-system/packages/frontend/src/components/SidePanel.tsx +55 -115
  66. package/templates/assistkick-product-system/packages/frontend/src/components/TerminalView.tsx +121 -52
  67. package/templates/assistkick-product-system/packages/frontend/src/components/Toolbar.tsx +155 -77
  68. package/templates/assistkick-product-system/packages/frontend/src/components/UsersView.tsx +52 -52
  69. package/templates/assistkick-product-system/packages/frontend/src/components/VideoGallery.tsx +313 -0
  70. package/templates/assistkick-product-system/packages/frontend/src/components/VideographyView.tsx +250 -0
  71. package/templates/assistkick-product-system/packages/frontend/src/components/WorkflowsView.tsx +474 -0
  72. package/templates/assistkick-product-system/packages/frontend/src/components/ds/AccentBorderList.tsx +53 -0
  73. package/templates/assistkick-product-system/packages/frontend/src/components/ds/Button.tsx +87 -0
  74. package/templates/assistkick-product-system/packages/frontend/src/components/ds/ButtonGroup.tsx +29 -0
  75. package/templates/assistkick-product-system/packages/frontend/src/components/ds/ButtonShowcase.tsx +221 -0
  76. package/templates/assistkick-product-system/packages/frontend/src/components/ds/CardGlass.tsx +141 -0
  77. package/templates/assistkick-product-system/packages/frontend/src/components/ds/CompletionRing.tsx +30 -0
  78. package/templates/assistkick-product-system/packages/frontend/src/components/ds/ContentCard.tsx +34 -0
  79. package/templates/assistkick-product-system/packages/frontend/src/components/ds/IconButton.tsx +74 -0
  80. package/templates/assistkick-product-system/packages/frontend/src/components/ds/KanbanCard.tsx +270 -0
  81. package/templates/assistkick-product-system/packages/frontend/src/components/ds/KanbanCardShowcase.tsx +37 -0
  82. package/templates/assistkick-product-system/packages/frontend/src/components/ds/Kbd.tsx +11 -0
  83. package/templates/assistkick-product-system/packages/frontend/src/components/ds/KindBadge.tsx +21 -0
  84. package/templates/assistkick-product-system/packages/frontend/src/components/ds/NavBarSidekick.tsx +207 -0
  85. package/templates/assistkick-product-system/packages/frontend/src/components/ds/SidePanelShowcase.tsx +370 -0
  86. package/templates/assistkick-product-system/packages/frontend/src/components/ds/SideSheet.tsx +64 -0
  87. package/templates/assistkick-product-system/packages/frontend/src/components/ds/StatusDot.tsx +18 -0
  88. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/CheckCardPositionNode.tsx +36 -0
  89. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/CheckCycleCountNode.tsx +60 -0
  90. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/EndNode.tsx +42 -0
  91. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/GroupNode.tsx +189 -0
  92. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/NodePalette.tsx +123 -0
  93. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/RunAgentNode.tsx +51 -0
  94. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/SetCardMetadataNode.tsx +53 -0
  95. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/StartNode.tsx +18 -0
  96. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/TransitionCardNode.tsx +59 -0
  97. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/WorkflowCanvas.tsx +335 -0
  98. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/WorkflowMonitorModal.tsx +634 -0
  99. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/autoLayout.ts +103 -0
  100. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/edgeColors.ts +35 -0
  101. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/monitor_nodes.tsx +208 -0
  102. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/workflow_types.test.ts +119 -0
  103. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/workflow_types.ts +107 -0
  104. package/templates/assistkick-product-system/packages/frontend/src/constants/graph.ts +13 -11
  105. package/templates/assistkick-product-system/packages/frontend/src/hooks/useAutoSave.ts +75 -0
  106. package/templates/assistkick-product-system/packages/frontend/src/hooks/useGraph.ts +6 -21
  107. package/templates/assistkick-product-system/packages/frontend/src/hooks/useProjects.ts +15 -80
  108. package/templates/assistkick-product-system/packages/frontend/src/hooks/useToast.tsx +16 -3
  109. package/templates/assistkick-product-system/packages/frontend/src/pages/accept_invitation_page.tsx +30 -27
  110. package/templates/assistkick-product-system/packages/frontend/src/pages/forgot_password_page.tsx +18 -15
  111. package/templates/assistkick-product-system/packages/frontend/src/pages/register_page.tsx +21 -18
  112. package/templates/assistkick-product-system/packages/frontend/src/pages/reset_password_page.tsx +28 -25
  113. package/templates/assistkick-product-system/packages/frontend/src/routes/AgentsRoute.tsx +6 -0
  114. package/templates/assistkick-product-system/packages/frontend/src/routes/CoherenceRoute.tsx +19 -0
  115. package/templates/assistkick-product-system/packages/frontend/src/routes/DashboardLayout.tsx +54 -0
  116. package/templates/assistkick-product-system/packages/frontend/src/routes/DesignSystemRoute.tsx +6 -0
  117. package/templates/assistkick-product-system/packages/frontend/src/routes/FilesRoute.tsx +13 -0
  118. package/templates/assistkick-product-system/packages/frontend/src/routes/GraphRoute.tsx +93 -0
  119. package/templates/assistkick-product-system/packages/frontend/src/routes/KanbanRoute.tsx +30 -0
  120. package/templates/assistkick-product-system/packages/frontend/src/routes/TerminalRoute.tsx +9 -0
  121. package/templates/assistkick-product-system/packages/frontend/src/routes/UsersRoute.tsx +6 -0
  122. package/templates/assistkick-product-system/packages/frontend/src/routes/VideographyRoute.tsx +13 -0
  123. package/templates/assistkick-product-system/packages/frontend/src/routes/WorkflowsRoute.tsx +6 -0
  124. package/templates/assistkick-product-system/packages/frontend/src/stores/useGitModalStore.ts +14 -0
  125. package/templates/assistkick-product-system/packages/frontend/src/stores/useGraphStore.ts +36 -0
  126. package/templates/assistkick-product-system/packages/frontend/src/stores/useGraphUIStore.ts +25 -0
  127. package/templates/assistkick-product-system/packages/frontend/src/stores/useProjectStore.ts +90 -0
  128. package/templates/assistkick-product-system/packages/frontend/src/stores/useQaSheetStore.ts +27 -0
  129. package/templates/assistkick-product-system/packages/frontend/src/stores/useSidePanelStore.ts +76 -0
  130. package/templates/assistkick-product-system/packages/frontend/src/styles/index.css +336 -3632
  131. package/templates/assistkick-product-system/packages/frontend/src/utils/auto_save_service.test.ts +167 -0
  132. package/templates/assistkick-product-system/packages/frontend/src/utils/auto_save_service.ts +101 -0
  133. package/templates/assistkick-product-system/packages/frontend/src/utils/composition_matcher.test.ts +42 -0
  134. package/templates/assistkick-product-system/packages/frontend/src/utils/composition_matcher.ts +17 -0
  135. package/templates/assistkick-product-system/packages/frontend/src/utils/file_utils.test.ts +145 -0
  136. package/templates/assistkick-product-system/packages/frontend/src/utils/file_utils.ts +42 -0
  137. package/templates/assistkick-product-system/packages/frontend/src/utils/task_status.test.ts +4 -10
  138. package/templates/assistkick-product-system/packages/frontend/src/utils/task_status.ts +19 -1
  139. package/templates/assistkick-product-system/packages/frontend/vite.config.ts +7 -1
  140. package/templates/assistkick-product-system/packages/shared/db/local.db +0 -0
  141. package/templates/assistkick-product-system/packages/shared/db/migrations/0004_tidy_matthew_murdock.sql +9 -0
  142. package/templates/assistkick-product-system/packages/shared/db/migrations/0005_mysterious_falcon.sql +692 -0
  143. package/templates/assistkick-product-system/packages/shared/db/migrations/0006_next_venom.sql +9 -0
  144. package/templates/assistkick-product-system/packages/shared/db/migrations/0007_deep_barracuda.sql +39 -0
  145. package/templates/assistkick-product-system/packages/shared/db/migrations/0008_puzzling_hannibal_king.sql +1 -0
  146. package/templates/assistkick-product-system/packages/shared/db/migrations/0009_amused_beast.sql +8 -0
  147. package/templates/assistkick-product-system/packages/shared/db/migrations/0010_spotty_moira_mactaggert.sql +9 -0
  148. package/templates/assistkick-product-system/packages/shared/db/migrations/0011_goofy_snowbird.sql +3 -0
  149. package/templates/assistkick-product-system/packages/shared/db/migrations/0011_supreme_doctor_octopus.sql +3 -0
  150. package/templates/assistkick-product-system/packages/shared/db/migrations/0013_reflective_prowler.sql +15 -0
  151. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0004_snapshot.json +921 -0
  152. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0005_snapshot.json +1042 -0
  153. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0006_snapshot.json +1101 -0
  154. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0007_snapshot.json +1336 -0
  155. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0008_snapshot.json +1275 -0
  156. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0009_snapshot.json +1327 -0
  157. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0010_snapshot.json +1393 -0
  158. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0011_snapshot.json +1436 -0
  159. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0013_snapshot.json +1538 -0
  160. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/_journal.json +70 -0
  161. package/templates/assistkick-product-system/packages/shared/db/schema.ts +113 -0
  162. package/templates/assistkick-product-system/packages/shared/lib/claude-service.ts +32 -7
  163. package/templates/assistkick-product-system/packages/shared/lib/constants.ts +9 -0
  164. package/templates/assistkick-product-system/packages/shared/lib/git_workflow.ts +12 -4
  165. package/templates/assistkick-product-system/packages/shared/lib/graph.ts +16 -5
  166. package/templates/assistkick-product-system/packages/shared/lib/workflow_engine.test.ts +1753 -0
  167. package/templates/assistkick-product-system/packages/shared/lib/workflow_engine.ts +1281 -0
  168. package/templates/assistkick-product-system/packages/shared/lib/workflow_orchestrator.ts +211 -0
  169. package/templates/assistkick-product-system/packages/shared/tools/add_node.test.ts +43 -0
  170. package/templates/assistkick-product-system/packages/shared/tools/add_node.ts +13 -2
  171. package/templates/assistkick-product-system/packages/shared/tools/get_kanban.ts +1 -1
  172. package/templates/assistkick-product-system/packages/shared/tools/migrate_epics.test.ts +226 -0
  173. package/templates/assistkick-product-system/packages/shared/tools/migrate_epics.ts +251 -0
  174. package/templates/assistkick-product-system/packages/shared/tools/update_node.ts +2 -2
  175. package/templates/assistkick-product-system/packages/shared/utils/hello_workflow.test.ts +10 -0
  176. package/templates/assistkick-product-system/packages/shared/utils/hello_workflow.ts +6 -0
  177. package/templates/assistkick-product-system/packages/video/Root.tsx +85 -0
  178. package/templates/assistkick-product-system/packages/video/components/email_scene.tsx +231 -0
  179. package/templates/assistkick-product-system/packages/video/components/outro_scene.tsx +153 -0
  180. package/templates/assistkick-product-system/packages/video/components/part_divider.tsx +90 -0
  181. package/templates/assistkick-product-system/packages/video/components/scene.tsx +226 -0
  182. package/templates/assistkick-product-system/packages/video/components/theme.ts +22 -0
  183. package/templates/assistkick-product-system/packages/video/components/title_scene.tsx +169 -0
  184. package/templates/assistkick-product-system/packages/video/components/video_split_layout.tsx +84 -0
  185. package/templates/assistkick-product-system/packages/video/compositions/.gitkeep +0 -0
  186. package/templates/assistkick-product-system/packages/video/index.ts +4 -0
  187. package/templates/assistkick-product-system/packages/video/package.json +28 -0
  188. package/templates/assistkick-product-system/packages/video/remotion.config.ts +11 -0
  189. package/templates/assistkick-product-system/packages/video/scripts/process_script.test.ts +326 -0
  190. package/templates/assistkick-product-system/packages/video/scripts/process_script.ts +630 -0
  191. package/templates/assistkick-product-system/packages/video/style.css +1 -0
  192. package/templates/assistkick-product-system/packages/video/tsconfig.json +18 -0
  193. package/templates/assistkick-product-system/tests/graph_legend.test.ts +2 -1
  194. package/templates/assistkick-product-system/tests/video_render_service.test.ts +179 -0
  195. package/templates/assistkick-product-system/tests/web_terminal.test.ts +219 -455
  196. package/templates/assistkick-product-system/tests/workflow_integration.test.ts +341 -0
  197. package/templates/skills/assistkick-bootstrap/SKILL.md +3 -3
  198. package/templates/skills/assistkick-code-reviewer/SKILL.md +2 -2
  199. package/templates/skills/assistkick-debugger/SKILL.md +2 -2
  200. package/templates/skills/assistkick-developer/SKILL.md +6 -3
  201. package/templates/skills/assistkick-developer/references/react_development_guidelines.md +225 -0
  202. package/templates/skills/assistkick-interview/SKILL.md +2 -2
  203. package/templates/skills/product-system/graph.json +1890 -0
  204. package/templates/skills/product-system/kanban.json +304 -0
  205. package/templates/skills/product-system/nodes/comp_001.md +56 -0
  206. package/templates/skills/product-system/nodes/comp_002.md +57 -0
  207. package/templates/skills/product-system/nodes/data_001.md +51 -0
  208. package/templates/skills/product-system/nodes/data_002.md +40 -0
  209. package/templates/skills/product-system/nodes/data_004.md +38 -0
  210. package/templates/skills/product-system/nodes/dec_001.md +34 -0
  211. package/templates/skills/product-system/nodes/dec_016.md +32 -0
  212. package/templates/skills/product-system/nodes/feat_008.md +30 -0
  213. package/templates/skills/video-composition-agent/SKILL.md +232 -0
  214. package/templates/skills/video-script-writer/SKILL.md +136 -0
@@ -0,0 +1,275 @@
1
+ import { describe, it, mock, beforeEach } from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { WorkflowService } from './workflow_service.ts';
4
+
5
+ const MOCK_WORKFLOW = {
6
+ id: 'wf-1',
7
+ name: 'Test Workflow',
8
+ description: 'A test workflow',
9
+ projectId: null,
10
+ featureType: null,
11
+ isDefault: 0,
12
+ graphData: '{"nodes":[],"edges":[]}',
13
+ createdAt: '2024-01-01T00:00:00Z',
14
+ updatedAt: '2024-01-01T00:00:00Z',
15
+ };
16
+
17
+ const MOCK_DEFAULT_WORKFLOW = {
18
+ id: 'wf-default',
19
+ name: 'Default Pipeline',
20
+ description: 'Default workflow',
21
+ projectId: null,
22
+ featureType: null,
23
+ isDefault: 1,
24
+ graphData: '{"nodes":[{"id":"start_1"}],"edges":[]}',
25
+ createdAt: '2024-01-01T00:00:00Z',
26
+ updatedAt: '2024-01-01T00:00:00Z',
27
+ };
28
+
29
+ const createMockDb = () => {
30
+ let selectResults: any[][] = [];
31
+ let selectCallIndex = 0;
32
+
33
+ const db = {
34
+ select: mock.fn(() => {
35
+ const selectObj = {
36
+ from: mock.fn(() => {
37
+ const fromObj = {
38
+ where: mock.fn(() => {
39
+ const result = selectResults[selectCallIndex] || [];
40
+ selectCallIndex++;
41
+ return result;
42
+ }),
43
+ };
44
+ return fromObj;
45
+ }),
46
+ };
47
+ return selectObj;
48
+ }),
49
+ insert: mock.fn(() => ({
50
+ values: mock.fn(() => Promise.resolve()),
51
+ })),
52
+ update: mock.fn(() => ({
53
+ set: mock.fn(() => ({
54
+ where: mock.fn(() => Promise.resolve()),
55
+ })),
56
+ })),
57
+ delete: mock.fn(() => ({
58
+ where: mock.fn(() => Promise.resolve()),
59
+ })),
60
+ _setSelectResults: (results: any[][]) => {
61
+ selectResults = results;
62
+ selectCallIndex = 0;
63
+ },
64
+ };
65
+
66
+ return db;
67
+ };
68
+
69
+ const createService = (db: any) => {
70
+ return new WorkflowService({
71
+ getDb: () => db,
72
+ log: mock.fn(),
73
+ });
74
+ };
75
+
76
+ describe('WorkflowService', () => {
77
+ let db: ReturnType<typeof createMockDb>;
78
+ let service: WorkflowService;
79
+
80
+ beforeEach(() => {
81
+ db = createMockDb();
82
+ service = createService(db);
83
+ });
84
+
85
+ describe('list', () => {
86
+ it('returns global workflows when no projectId', async () => {
87
+ db._setSelectResults([[MOCK_DEFAULT_WORKFLOW]]);
88
+
89
+ const result = await service.list();
90
+
91
+ assert.equal(result.length, 1);
92
+ assert.equal(db.select.mock.calls.length, 1);
93
+ });
94
+
95
+ it('returns global + project workflows when projectId provided', async () => {
96
+ const projectWorkflow = { ...MOCK_WORKFLOW, projectId: 'proj_001' };
97
+ db._setSelectResults([[MOCK_DEFAULT_WORKFLOW, projectWorkflow]]);
98
+
99
+ const result = await service.list('proj_001');
100
+
101
+ assert.equal(result.length, 2);
102
+ });
103
+ });
104
+
105
+ describe('getById', () => {
106
+ it('returns workflow when found', async () => {
107
+ db._setSelectResults([[MOCK_WORKFLOW]]);
108
+
109
+ const result = await service.getById('wf-1');
110
+
111
+ assert.ok(result);
112
+ assert.equal(result.name, 'Test Workflow');
113
+ assert.equal(result.graphData, '{"nodes":[],"edges":[]}');
114
+ });
115
+
116
+ it('returns null when not found', async () => {
117
+ db._setSelectResults([[]]);
118
+
119
+ const result = await service.getById('nonexistent');
120
+
121
+ assert.equal(result, null);
122
+ });
123
+ });
124
+
125
+ describe('create', () => {
126
+ it('creates a workflow with correct fields', async () => {
127
+ const result = await service.create({
128
+ name: 'New Workflow',
129
+ graphData: '{"nodes":[],"edges":[]}',
130
+ });
131
+
132
+ assert.ok(result.id);
133
+ assert.equal(result.name, 'New Workflow');
134
+ assert.equal(result.projectId, null);
135
+ assert.equal(result.isDefault, 0);
136
+ assert.equal(result.graphData, '{"nodes":[],"edges":[]}');
137
+ assert.ok(result.createdAt);
138
+ assert.equal(db.insert.mock.calls.length, 1);
139
+ });
140
+
141
+ it('creates a project-scoped workflow', async () => {
142
+ const result = await service.create({
143
+ name: 'Project Workflow',
144
+ graphData: '{"nodes":[],"edges":[]}',
145
+ projectId: 'proj_001',
146
+ });
147
+
148
+ assert.equal(result.projectId, 'proj_001');
149
+ });
150
+
151
+ it('sets description when provided', async () => {
152
+ const result = await service.create({
153
+ name: 'Described Workflow',
154
+ description: 'With description',
155
+ graphData: '{"nodes":[],"edges":[]}',
156
+ });
157
+
158
+ assert.equal(result.description, 'With description');
159
+ });
160
+
161
+ it('creates a workflow with featureType', async () => {
162
+ const result = await service.create({
163
+ name: 'Video Workflow',
164
+ graphData: '{"nodes":[],"edges":[]}',
165
+ featureType: 'video',
166
+ });
167
+
168
+ assert.equal(result.featureType, 'video');
169
+ });
170
+
171
+ it('defaults featureType to null when not provided', async () => {
172
+ const result = await service.create({
173
+ name: 'Generic Workflow',
174
+ graphData: '{"nodes":[],"edges":[]}',
175
+ });
176
+
177
+ assert.equal(result.featureType, null);
178
+ });
179
+ });
180
+
181
+ describe('update', () => {
182
+ it('updates workflow name', async () => {
183
+ db._setSelectResults([[MOCK_WORKFLOW]]);
184
+
185
+ const result = await service.update('wf-1', { name: 'Updated Name' });
186
+
187
+ assert.equal(result.name, 'Updated Name');
188
+ assert.equal(db.update.mock.calls.length, 1);
189
+ });
190
+
191
+ it('updates workflow graphData', async () => {
192
+ db._setSelectResults([[MOCK_WORKFLOW]]);
193
+
194
+ const result = await service.update('wf-1', { graphData: '{"nodes":[{"id":"new"}],"edges":[]}' });
195
+
196
+ assert.equal(result.graphData, '{"nodes":[{"id":"new"}],"edges":[]}');
197
+ });
198
+
199
+ it('updates workflow description', async () => {
200
+ db._setSelectResults([[MOCK_WORKFLOW]]);
201
+
202
+ const result = await service.update('wf-1', { description: 'New description' });
203
+
204
+ assert.equal(result.description, 'New description');
205
+ });
206
+
207
+ it('updates workflow featureType', async () => {
208
+ db._setSelectResults([[MOCK_WORKFLOW]]);
209
+
210
+ const result = await service.update('wf-1', { featureType: 'video' });
211
+
212
+ assert.equal(result.featureType, 'video');
213
+ });
214
+
215
+ it('throws when workflow not found', async () => {
216
+ db._setSelectResults([[]]);
217
+
218
+ await assert.rejects(
219
+ () => service.update('nonexistent', { name: 'X' }),
220
+ { message: 'Workflow not found' },
221
+ );
222
+ });
223
+ });
224
+
225
+ describe('delete', () => {
226
+ it('deletes a workflow with no active executions', async () => {
227
+ // First: getById, second: check active executions
228
+ db._setSelectResults([[MOCK_WORKFLOW], []]);
229
+
230
+ await service.delete('wf-1');
231
+
232
+ assert.equal(db.delete.mock.calls.length, 1);
233
+ });
234
+
235
+ it('throws when workflow not found', async () => {
236
+ db._setSelectResults([[]]);
237
+
238
+ await assert.rejects(
239
+ () => service.delete('nonexistent'),
240
+ { message: 'Workflow not found' },
241
+ );
242
+ });
243
+
244
+ it('throws when workflow has active executions', async () => {
245
+ const activeExecution = { id: 'exec-1', workflowId: 'wf-1', status: 'running' };
246
+ db._setSelectResults([[MOCK_WORKFLOW], [activeExecution]]);
247
+
248
+ await assert.rejects(
249
+ () => service.delete('wf-1'),
250
+ { message: 'Cannot delete a workflow with active executions' },
251
+ );
252
+ });
253
+ });
254
+
255
+ describe('setDefault', () => {
256
+ it('sets a workflow as default', async () => {
257
+ db._setSelectResults([[MOCK_WORKFLOW]]);
258
+
259
+ const result = await service.setDefault('wf-1');
260
+
261
+ assert.equal(result.isDefault, 1);
262
+ // Two update calls: one to unset previous default, one to set new
263
+ assert.equal(db.update.mock.calls.length, 2);
264
+ });
265
+
266
+ it('throws when workflow not found', async () => {
267
+ db._setSelectResults([[]]);
268
+
269
+ await assert.rejects(
270
+ () => service.setDefault('nonexistent'),
271
+ { message: 'Workflow not found' },
272
+ );
273
+ });
274
+ });
275
+ });
@@ -0,0 +1,222 @@
1
+ /**
2
+ * Workflow service — CRUD operations for workflows.
3
+ * Handles listing, creating, updating, deleting workflows,
4
+ * and managing the is_default flag with scope-level uniqueness.
5
+ */
6
+
7
+ import { eq, and, isNull, or } from 'drizzle-orm';
8
+ import { workflows, workflowExecutions } from '@assistkick/shared/db/schema.js';
9
+ import { randomUUID } from 'node:crypto';
10
+
11
+ interface WorkflowServiceDeps {
12
+ getDb: () => any;
13
+ log: (tag: string, ...args: any[]) => void;
14
+ }
15
+
16
+ export interface WorkflowRecord {
17
+ id: string;
18
+ name: string;
19
+ description: string | null;
20
+ projectId: string | null;
21
+ featureType: string | null;
22
+ isDefault: number;
23
+ graphData: string;
24
+ createdAt: string;
25
+ updatedAt: string;
26
+ }
27
+
28
+ export interface WorkflowListItem {
29
+ id: string;
30
+ name: string;
31
+ description: string | null;
32
+ projectId: string | null;
33
+ featureType: string | null;
34
+ isDefault: number;
35
+ createdAt: string;
36
+ updatedAt: string;
37
+ }
38
+
39
+ export class WorkflowService {
40
+ private readonly getDb: () => any;
41
+ private readonly log: (tag: string, ...args: any[]) => void;
42
+
43
+ constructor({ getDb, log }: WorkflowServiceDeps) {
44
+ this.getDb = getDb;
45
+ this.log = log;
46
+ }
47
+
48
+ /** List workflows visible to the given scope (global + project-specific). Excludes graph_data. */
49
+ list = async (projectId?: string): Promise<WorkflowListItem[]> => {
50
+ const db = this.getDb();
51
+
52
+ const condition = projectId
53
+ ? or(isNull(workflows.projectId), eq(workflows.projectId, projectId))
54
+ : isNull(workflows.projectId);
55
+
56
+ const rows = await db
57
+ .select({
58
+ id: workflows.id,
59
+ name: workflows.name,
60
+ description: workflows.description,
61
+ projectId: workflows.projectId,
62
+ featureType: workflows.featureType,
63
+ isDefault: workflows.isDefault,
64
+ createdAt: workflows.createdAt,
65
+ updatedAt: workflows.updatedAt,
66
+ })
67
+ .from(workflows)
68
+ .where(condition);
69
+
70
+ this.log('WORKFLOWS', `Listed ${rows.length} workflows`);
71
+ return rows;
72
+ };
73
+
74
+ /** Get a single workflow by ID, including graph_data. */
75
+ getById = async (id: string): Promise<WorkflowRecord | null> => {
76
+ const db = this.getDb();
77
+ const [row] = await db.select().from(workflows).where(eq(workflows.id, id));
78
+ return row || null;
79
+ };
80
+
81
+ /** Create a new workflow. */
82
+ create = async (data: {
83
+ name: string;
84
+ description?: string | null;
85
+ projectId?: string | null;
86
+ featureType?: string | null;
87
+ graphData: string;
88
+ }): Promise<WorkflowRecord> => {
89
+ const db = this.getDb();
90
+ const now = new Date().toISOString();
91
+ const id = randomUUID();
92
+
93
+ const record: WorkflowRecord = {
94
+ id,
95
+ name: data.name,
96
+ description: data.description || null,
97
+ projectId: data.projectId || null,
98
+ featureType: data.featureType || null,
99
+ isDefault: 0,
100
+ graphData: data.graphData,
101
+ createdAt: now,
102
+ updatedAt: now,
103
+ };
104
+
105
+ await db.insert(workflows).values(record);
106
+ this.log('WORKFLOWS', `Created workflow: ${data.name} (${id})`);
107
+ return record;
108
+ };
109
+
110
+ /** Update a workflow's name, description, featureType, and/or graph_data. */
111
+ update = async (id: string, data: {
112
+ name?: string;
113
+ description?: string;
114
+ featureType?: string | null;
115
+ graphData?: string;
116
+ }): Promise<WorkflowRecord> => {
117
+ const db = this.getDb();
118
+ const existing = await this.getById(id);
119
+
120
+ if (!existing) {
121
+ throw new Error('Workflow not found');
122
+ }
123
+
124
+ const now = new Date().toISOString();
125
+ const updates: Record<string, any> = { updatedAt: now };
126
+
127
+ if (data.name !== undefined) updates.name = data.name;
128
+ if (data.description !== undefined) updates.description = data.description;
129
+ if (data.featureType !== undefined) updates.featureType = data.featureType;
130
+ if (data.graphData !== undefined) updates.graphData = data.graphData;
131
+
132
+ await db.update(workflows).set(updates).where(eq(workflows.id, id));
133
+
134
+ this.log('WORKFLOWS', `Updated workflow: ${existing.name} (${id})`);
135
+ return { ...existing, ...updates };
136
+ };
137
+
138
+ /** Delete a workflow. Blocked if it has active (running/pending) executions. */
139
+ delete = async (id: string): Promise<void> => {
140
+ const db = this.getDb();
141
+ const existing = await this.getById(id);
142
+
143
+ if (!existing) {
144
+ throw new Error('Workflow not found');
145
+ }
146
+
147
+ // Check for active executions
148
+ const activeExecutions = await db
149
+ .select()
150
+ .from(workflowExecutions)
151
+ .where(
152
+ and(
153
+ eq(workflowExecutions.workflowId, id),
154
+ or(
155
+ eq(workflowExecutions.status, 'running'),
156
+ eq(workflowExecutions.status, 'pending'),
157
+ ),
158
+ ),
159
+ );
160
+
161
+ if (activeExecutions.length > 0) {
162
+ throw new Error('Cannot delete a workflow with active executions');
163
+ }
164
+
165
+ await db.delete(workflows).where(eq(workflows.id, id));
166
+ this.log('WORKFLOWS', `Deleted workflow: ${existing.name} (${id})`);
167
+ };
168
+
169
+ /**
170
+ * Resolve the default workflow for a given project scope.
171
+ * Priority: project-specific default → global default.
172
+ * Returns null if no default workflow exists.
173
+ */
174
+ resolveDefault = async (projectId?: string): Promise<WorkflowRecord | null> => {
175
+ const db = this.getDb();
176
+
177
+ // Try project-specific default first
178
+ if (projectId) {
179
+ const [projectDefault] = await db.select().from(workflows).where(
180
+ and(eq(workflows.projectId, projectId), eq(workflows.isDefault, 1)),
181
+ );
182
+ if (projectDefault) return projectDefault;
183
+ }
184
+
185
+ // Fall back to global default
186
+ const [globalDefault] = await db.select().from(workflows).where(
187
+ and(isNull(workflows.projectId), eq(workflows.isDefault, 1)),
188
+ );
189
+ return globalDefault || null;
190
+ };
191
+
192
+ /** Set a workflow as the default for its scope, unsetting any previous default in the same scope. */
193
+ setDefault = async (id: string): Promise<WorkflowRecord> => {
194
+ const db = this.getDb();
195
+ const existing = await this.getById(id);
196
+
197
+ if (!existing) {
198
+ throw new Error('Workflow not found');
199
+ }
200
+
201
+ const now = new Date().toISOString();
202
+
203
+ // Unset previous default in the same scope (same project_id or both null)
204
+ const scopeCondition = existing.projectId
205
+ ? eq(workflows.projectId, existing.projectId)
206
+ : isNull(workflows.projectId);
207
+
208
+ await db
209
+ .update(workflows)
210
+ .set({ isDefault: 0, updatedAt: now })
211
+ .where(and(scopeCondition, eq(workflows.isDefault, 1)));
212
+
213
+ // Set this workflow as default
214
+ await db
215
+ .update(workflows)
216
+ .set({ isDefault: 1, updatedAt: now })
217
+ .where(eq(workflows.id, id));
218
+
219
+ this.log('WORKFLOWS', `Set default workflow: ${existing.name} (${id})`);
220
+ return { ...existing, isDefault: 1, updatedAt: now };
221
+ };
222
+ }
@@ -4,6 +4,9 @@
4
4
  <meta charset="UTF-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
6
  <link rel="icon" type="image/svg+xml" href="/favicon.svg">
7
+ <link rel="preconnect" href="https://fonts.googleapis.com">
8
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
9
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
7
10
  <title>Dev Product System</title>
8
11
  </head>
9
12
  <body>