@assistkick/create 1.7.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 (200) hide show
  1. package/dist/bin/create.js +0 -0
  2. package/package.json +9 -7
  3. package/templates/assistkick-product-system/.env.example +1 -0
  4. package/templates/assistkick-product-system/local.db +0 -0
  5. package/templates/assistkick-product-system/package.json +4 -2
  6. package/templates/assistkick-product-system/packages/backend/package.json +2 -0
  7. package/templates/assistkick-product-system/packages/backend/src/routes/agents.ts +165 -0
  8. package/templates/assistkick-product-system/packages/backend/src/routes/files.test.ts +358 -0
  9. package/templates/assistkick-product-system/packages/backend/src/routes/files.ts +356 -0
  10. package/templates/assistkick-product-system/packages/backend/src/routes/git.ts +96 -1
  11. package/templates/assistkick-product-system/packages/backend/src/routes/graph.ts +1 -0
  12. package/templates/assistkick-product-system/packages/backend/src/routes/kanban.ts +43 -4
  13. package/templates/assistkick-product-system/packages/backend/src/routes/pipeline.ts +200 -84
  14. package/templates/assistkick-product-system/packages/backend/src/routes/projects.ts +6 -3
  15. package/templates/assistkick-product-system/packages/backend/src/routes/terminal.ts +53 -17
  16. package/templates/assistkick-product-system/packages/backend/src/routes/video.ts +218 -0
  17. package/templates/assistkick-product-system/packages/backend/src/routes/workflow_groups.ts +119 -0
  18. package/templates/assistkick-product-system/packages/backend/src/routes/workflows.ts +154 -0
  19. package/templates/assistkick-product-system/packages/backend/src/server.ts +81 -9
  20. package/templates/assistkick-product-system/packages/backend/src/services/agent_service.test.ts +489 -0
  21. package/templates/assistkick-product-system/packages/backend/src/services/agent_service.ts +416 -0
  22. package/templates/assistkick-product-system/packages/backend/src/services/bundle_service.test.ts +189 -0
  23. package/templates/assistkick-product-system/packages/backend/src/services/bundle_service.ts +182 -0
  24. package/templates/assistkick-product-system/packages/backend/src/services/init.ts +28 -78
  25. package/templates/assistkick-product-system/packages/backend/src/services/project_service.test.ts +16 -0
  26. package/templates/assistkick-product-system/packages/backend/src/services/project_service.ts +73 -2
  27. package/templates/assistkick-product-system/packages/backend/src/services/project_workspace_service.test.ts +4 -4
  28. package/templates/assistkick-product-system/packages/backend/src/services/project_workspace_service.ts +87 -11
  29. package/templates/assistkick-product-system/packages/backend/src/services/pty_session_manager.test.ts +210 -69
  30. package/templates/assistkick-product-system/packages/backend/src/services/pty_session_manager.ts +210 -215
  31. package/templates/assistkick-product-system/packages/backend/src/services/ssh_key_service.test.ts +162 -0
  32. package/templates/assistkick-product-system/packages/backend/src/services/ssh_key_service.ts +148 -0
  33. package/templates/assistkick-product-system/packages/backend/src/services/terminal_ws_handler.ts +11 -5
  34. package/templates/assistkick-product-system/packages/backend/src/services/tts_service.test.ts +64 -0
  35. package/templates/assistkick-product-system/packages/backend/src/services/tts_service.ts +134 -0
  36. package/templates/assistkick-product-system/packages/backend/src/services/video_render_service.test.ts +256 -0
  37. package/templates/assistkick-product-system/packages/backend/src/services/video_render_service.ts +258 -0
  38. package/templates/assistkick-product-system/packages/backend/src/services/workflow_group_service.ts +106 -0
  39. package/templates/assistkick-product-system/packages/backend/src/services/workflow_service.test.ts +275 -0
  40. package/templates/assistkick-product-system/packages/backend/src/services/workflow_service.ts +222 -0
  41. package/templates/assistkick-product-system/packages/frontend/package-lock.json +3455 -0
  42. package/templates/assistkick-product-system/packages/frontend/package.json +6 -0
  43. package/templates/assistkick-product-system/packages/frontend/src/App.tsx +8 -0
  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 +20 -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 +187 -56
  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 +202 -171
  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 +32 -49
  65. package/templates/assistkick-product-system/packages/frontend/src/components/SidePanel.tsx +43 -48
  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 +20 -14
  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 +103 -87
  81. package/templates/assistkick-product-system/packages/frontend/src/components/ds/KanbanCardShowcase.tsx +9 -188
  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 +81 -37
  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/useToast.tsx +16 -3
  107. package/templates/assistkick-product-system/packages/frontend/src/pages/accept_invitation_page.tsx +30 -27
  108. package/templates/assistkick-product-system/packages/frontend/src/pages/forgot_password_page.tsx +18 -15
  109. package/templates/assistkick-product-system/packages/frontend/src/pages/register_page.tsx +21 -18
  110. package/templates/assistkick-product-system/packages/frontend/src/pages/reset_password_page.tsx +28 -25
  111. package/templates/assistkick-product-system/packages/frontend/src/routes/AgentsRoute.tsx +6 -0
  112. package/templates/assistkick-product-system/packages/frontend/src/routes/CoherenceRoute.tsx +1 -1
  113. package/templates/assistkick-product-system/packages/frontend/src/routes/DashboardLayout.tsx +2 -2
  114. package/templates/assistkick-product-system/packages/frontend/src/routes/FilesRoute.tsx +13 -0
  115. package/templates/assistkick-product-system/packages/frontend/src/routes/GraphRoute.tsx +2 -2
  116. package/templates/assistkick-product-system/packages/frontend/src/routes/VideographyRoute.tsx +13 -0
  117. package/templates/assistkick-product-system/packages/frontend/src/routes/WorkflowsRoute.tsx +6 -0
  118. package/templates/assistkick-product-system/packages/frontend/src/stores/useProjectStore.ts +6 -3
  119. package/templates/assistkick-product-system/packages/frontend/src/stores/useSidePanelStore.ts +4 -4
  120. package/templates/assistkick-product-system/packages/frontend/src/styles/index.css +275 -3535
  121. package/templates/assistkick-product-system/packages/frontend/src/utils/auto_save_service.test.ts +167 -0
  122. package/templates/assistkick-product-system/packages/frontend/src/utils/auto_save_service.ts +101 -0
  123. package/templates/assistkick-product-system/packages/frontend/src/utils/composition_matcher.test.ts +42 -0
  124. package/templates/assistkick-product-system/packages/frontend/src/utils/composition_matcher.ts +17 -0
  125. package/templates/assistkick-product-system/packages/frontend/src/utils/file_utils.test.ts +145 -0
  126. package/templates/assistkick-product-system/packages/frontend/src/utils/file_utils.ts +42 -0
  127. package/templates/assistkick-product-system/packages/frontend/src/utils/task_status.test.ts +4 -10
  128. package/templates/assistkick-product-system/packages/frontend/src/utils/task_status.ts +19 -1
  129. package/templates/assistkick-product-system/packages/frontend/vite.config.ts +5 -0
  130. package/templates/assistkick-product-system/packages/shared/db/local.db +0 -0
  131. package/templates/assistkick-product-system/packages/shared/db/migrations/0004_tidy_matthew_murdock.sql +9 -0
  132. package/templates/assistkick-product-system/packages/shared/db/migrations/0005_mysterious_falcon.sql +692 -0
  133. package/templates/assistkick-product-system/packages/shared/db/migrations/0006_next_venom.sql +9 -0
  134. package/templates/assistkick-product-system/packages/shared/db/migrations/0007_deep_barracuda.sql +39 -0
  135. package/templates/assistkick-product-system/packages/shared/db/migrations/0008_puzzling_hannibal_king.sql +1 -0
  136. package/templates/assistkick-product-system/packages/shared/db/migrations/0009_amused_beast.sql +8 -0
  137. package/templates/assistkick-product-system/packages/shared/db/migrations/0010_spotty_moira_mactaggert.sql +9 -0
  138. package/templates/assistkick-product-system/packages/shared/db/migrations/0011_goofy_snowbird.sql +3 -0
  139. package/templates/assistkick-product-system/packages/shared/db/migrations/0011_supreme_doctor_octopus.sql +3 -0
  140. package/templates/assistkick-product-system/packages/shared/db/migrations/0013_reflective_prowler.sql +15 -0
  141. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0004_snapshot.json +921 -0
  142. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0005_snapshot.json +1042 -0
  143. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0006_snapshot.json +1101 -0
  144. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0007_snapshot.json +1336 -0
  145. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0008_snapshot.json +1275 -0
  146. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0009_snapshot.json +1327 -0
  147. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0010_snapshot.json +1393 -0
  148. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0011_snapshot.json +1436 -0
  149. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0013_snapshot.json +1538 -0
  150. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/_journal.json +70 -0
  151. package/templates/assistkick-product-system/packages/shared/db/schema.ts +113 -0
  152. package/templates/assistkick-product-system/packages/shared/lib/claude-service.ts +32 -7
  153. package/templates/assistkick-product-system/packages/shared/lib/constants.ts +9 -0
  154. package/templates/assistkick-product-system/packages/shared/lib/git_workflow.ts +12 -4
  155. package/templates/assistkick-product-system/packages/shared/lib/graph.ts +5 -0
  156. package/templates/assistkick-product-system/packages/shared/lib/workflow_engine.test.ts +1753 -0
  157. package/templates/assistkick-product-system/packages/shared/lib/workflow_engine.ts +1281 -0
  158. package/templates/assistkick-product-system/packages/shared/lib/workflow_orchestrator.ts +211 -0
  159. package/templates/assistkick-product-system/packages/shared/tools/add_node.test.ts +43 -0
  160. package/templates/assistkick-product-system/packages/shared/tools/add_node.ts +13 -2
  161. package/templates/assistkick-product-system/packages/shared/tools/get_kanban.ts +1 -1
  162. package/templates/assistkick-product-system/packages/shared/tools/migrate_epics.test.ts +226 -0
  163. package/templates/assistkick-product-system/packages/shared/tools/migrate_epics.ts +251 -0
  164. package/templates/assistkick-product-system/packages/shared/tools/update_node.ts +2 -2
  165. package/templates/assistkick-product-system/packages/shared/utils/hello_workflow.test.ts +10 -0
  166. package/templates/assistkick-product-system/packages/shared/utils/hello_workflow.ts +6 -0
  167. package/templates/assistkick-product-system/packages/video/Root.tsx +85 -0
  168. package/templates/assistkick-product-system/packages/video/components/email_scene.tsx +231 -0
  169. package/templates/assistkick-product-system/packages/video/components/outro_scene.tsx +153 -0
  170. package/templates/assistkick-product-system/packages/video/components/part_divider.tsx +90 -0
  171. package/templates/assistkick-product-system/packages/video/components/scene.tsx +226 -0
  172. package/templates/assistkick-product-system/packages/video/components/theme.ts +22 -0
  173. package/templates/assistkick-product-system/packages/video/components/title_scene.tsx +169 -0
  174. package/templates/assistkick-product-system/packages/video/components/video_split_layout.tsx +84 -0
  175. package/templates/assistkick-product-system/packages/video/compositions/.gitkeep +0 -0
  176. package/templates/assistkick-product-system/packages/video/index.ts +4 -0
  177. package/templates/assistkick-product-system/packages/video/package.json +28 -0
  178. package/templates/assistkick-product-system/packages/video/remotion.config.ts +11 -0
  179. package/templates/assistkick-product-system/packages/video/scripts/process_script.test.ts +326 -0
  180. package/templates/assistkick-product-system/packages/video/scripts/process_script.ts +630 -0
  181. package/templates/assistkick-product-system/packages/video/style.css +1 -0
  182. package/templates/assistkick-product-system/packages/video/tsconfig.json +18 -0
  183. package/templates/assistkick-product-system/tests/graph_legend.test.ts +2 -1
  184. package/templates/assistkick-product-system/tests/video_render_service.test.ts +179 -0
  185. package/templates/assistkick-product-system/tests/web_terminal.test.ts +219 -455
  186. package/templates/assistkick-product-system/tests/workflow_integration.test.ts +341 -0
  187. package/templates/skills/assistkick-developer/SKILL.md +3 -0
  188. package/templates/skills/assistkick-developer/references/react_development_guidelines.md +225 -0
  189. package/templates/skills/product-system/graph.json +1890 -0
  190. package/templates/skills/product-system/kanban.json +304 -0
  191. package/templates/skills/product-system/nodes/comp_001.md +56 -0
  192. package/templates/skills/product-system/nodes/comp_002.md +57 -0
  193. package/templates/skills/product-system/nodes/data_001.md +51 -0
  194. package/templates/skills/product-system/nodes/data_002.md +40 -0
  195. package/templates/skills/product-system/nodes/data_004.md +38 -0
  196. package/templates/skills/product-system/nodes/dec_001.md +34 -0
  197. package/templates/skills/product-system/nodes/dec_016.md +32 -0
  198. package/templates/skills/product-system/nodes/feat_008.md +30 -0
  199. package/templates/skills/video-composition-agent/SKILL.md +232 -0
  200. package/templates/skills/video-script-writer/SKILL.md +136 -0
@@ -0,0 +1,103 @@
1
+ import type { Node, Edge } from '@xyflow/react';
2
+
3
+ const NODE_WIDTH = 200;
4
+ const NODE_HEIGHT = 160;
5
+ const HORIZONTAL_GAP = 60;
6
+ const VERTICAL_GAP = 80;
7
+
8
+ /**
9
+ * Auto-layout nodes as a top-down tree using BFS from root nodes.
10
+ * Nodes at the same depth are placed in the same row.
11
+ * Handles DAGs with multiple parents (node goes to the deepest level).
12
+ * Back-edges (cycles) are ignored during layout.
13
+ */
14
+ export function autoLayoutNodes(nodes: Node[], edges: Edge[]): Node[] {
15
+ if (nodes.length === 0) return nodes;
16
+
17
+ // Build adjacency list (source -> targets)
18
+ const children = new Map<string, string[]>();
19
+ const parentCount = new Map<string, number>();
20
+ for (const node of nodes) {
21
+ children.set(node.id, []);
22
+ parentCount.set(node.id, 0);
23
+ }
24
+ for (const edge of edges) {
25
+ if (children.has(edge.source) && parentCount.has(edge.target)) {
26
+ children.get(edge.source)!.push(edge.target);
27
+ parentCount.set(edge.target, (parentCount.get(edge.target) || 0) + 1);
28
+ }
29
+ }
30
+
31
+ // Find root nodes (no incoming edges) — typically the start node
32
+ const roots = nodes.filter((n) => (parentCount.get(n.id) || 0) === 0);
33
+ if (roots.length === 0) {
34
+ // Fallback: use start-type nodes, or first node
35
+ const startNodes = nodes.filter((n) => n.type === 'start');
36
+ roots.push(...(startNodes.length > 0 ? startNodes : [nodes[0]]));
37
+ }
38
+
39
+ // BFS to assign depth levels — a node's depth is the longest path from any root.
40
+ // Back-edges (cycles) are detected and skipped to prevent infinite loops.
41
+ const depth = new Map<string, number>();
42
+ const enqueued = new Map<string, number>(); // track how many times a node was enqueued
43
+ const maxDepth = nodes.length; // no valid path can exceed the number of nodes
44
+ const queue: Array<{ id: string; d: number }> = [];
45
+ for (const root of roots) {
46
+ depth.set(root.id, 0);
47
+ enqueued.set(root.id, 1);
48
+ queue.push({ id: root.id, d: 0 });
49
+ }
50
+
51
+ while (queue.length > 0) {
52
+ const { id, d } = queue.shift()!;
53
+ for (const childId of children.get(id) || []) {
54
+ const newDepth = d + 1;
55
+ // Skip back-edges that would exceed the maximum possible depth
56
+ if (newDepth > maxDepth) continue;
57
+ const currentDepth = depth.get(childId);
58
+ // Use the maximum depth (longest path) to handle DAGs
59
+ if (currentDepth === undefined || newDepth > currentDepth) {
60
+ depth.set(childId, newDepth);
61
+ queue.push({ id: childId, d: newDepth });
62
+ }
63
+ }
64
+ }
65
+
66
+ // Assign any unvisited nodes (disconnected) to depth 0
67
+ for (const node of nodes) {
68
+ if (!depth.has(node.id)) {
69
+ depth.set(node.id, 0);
70
+ }
71
+ }
72
+
73
+ // Group nodes by depth level
74
+ const levels = new Map<number, Node[]>();
75
+ for (const node of nodes) {
76
+ const d = depth.get(node.id)!;
77
+ if (!levels.has(d)) levels.set(d, []);
78
+ levels.get(d)!.push(node);
79
+ }
80
+
81
+ // Sort levels by depth
82
+ const sortedDepths = [...levels.keys()].sort((a, b) => a - b);
83
+
84
+ // Position nodes: center each row horizontally, stack rows vertically
85
+ const positioned = new Map<string, { x: number; y: number }>();
86
+ for (const d of sortedDepths) {
87
+ const levelNodes = levels.get(d)!;
88
+ const rowWidth = levelNodes.length * NODE_WIDTH + (levelNodes.length - 1) * HORIZONTAL_GAP;
89
+ const startX = -rowWidth / 2;
90
+
91
+ for (let i = 0; i < levelNodes.length; i++) {
92
+ positioned.set(levelNodes[i].id, {
93
+ x: startX + i * (NODE_WIDTH + HORIZONTAL_GAP),
94
+ y: d * (NODE_HEIGHT + VERTICAL_GAP),
95
+ });
96
+ }
97
+ }
98
+
99
+ return nodes.map((node) => ({
100
+ ...node,
101
+ position: positioned.get(node.id) || node.position,
102
+ }));
103
+ }
@@ -0,0 +1,35 @@
1
+ import type { Edge } from '@xyflow/react';
2
+
3
+ const EDGE_COLORS = [
4
+ '#b5e853', // lime green
5
+ '#f06292', // pink
6
+ '#4fc3f7', // sky blue
7
+ '#ffb74d', // orange
8
+ '#ce93d8', // purple
9
+ '#4db6ac', // teal
10
+ '#fff176', // yellow
11
+ '#e57373', // coral red
12
+ '#90a4ae', // blue grey
13
+ '#aed581', // light green
14
+ ];
15
+
16
+ /**
17
+ * Assigns a unique color to each outgoing edge of a node from a 10-color swatch.
18
+ * Edges from the same source get sequential colors so they're visually distinct.
19
+ */
20
+ export const colorizeEdges = (edges: Edge[]): Edge[] => {
21
+ // Group edges by source, preserving insertion order
22
+ const sourceIndex = new Map<string, number>();
23
+
24
+ return edges.map((edge) => {
25
+ const idx = sourceIndex.get(edge.source) ?? 0;
26
+ sourceIndex.set(edge.source, idx + 1);
27
+ const color = EDGE_COLORS[idx % EDGE_COLORS.length];
28
+
29
+ return {
30
+ ...edge,
31
+ style: { ...edge.style, stroke: color, strokeWidth: 2 },
32
+ markerEnd: { type: 'arrowclosed' as const, color },
33
+ };
34
+ });
35
+ };
@@ -0,0 +1,208 @@
1
+ /**
2
+ * Lightweight read-only node components for the Workflow Monitor.
3
+ * These re-render with execution state but are non-interactive (no editing).
4
+ * They include proper Handles so React Flow can render edges correctly.
5
+ */
6
+
7
+ import React from 'react';
8
+ import { Handle, Position } from '@xyflow/react';
9
+ import type { NodeProps } from '@xyflow/react';
10
+ import {
11
+ Play, Square, ArrowLeftRight, Bot, MapPin, RefreshCw, Tag, Layers,
12
+ CircleDot, CheckCircle2, AlertTriangle, Clock,
13
+ } from 'lucide-react';
14
+
15
+ /* ── Execution status styling ── */
16
+
17
+ const STATUS_RING: Record<string, string> = {
18
+ running: 'ring-2 ring-accent animate-pulse',
19
+ completed: 'ring-2 ring-emerald-400',
20
+ failed: 'ring-2 ring-error',
21
+ };
22
+
23
+ const STATUS_BG: Record<string, string> = {
24
+ running: 'bg-accent/10',
25
+ completed: 'bg-emerald-500/10',
26
+ failed: 'bg-error/10',
27
+ pending: 'bg-white/[0.03]',
28
+ };
29
+
30
+ function statusIcon(status: string, size = 14) {
31
+ if (status === 'running') return <CircleDot size={size} className="text-accent animate-pulse" />;
32
+ if (status === 'completed') return <CheckCircle2 size={size} className="text-emerald-400" />;
33
+ if (status === 'failed') return <AlertTriangle size={size} className="text-error" />;
34
+ return <Clock size={size} className="text-content-muted" />;
35
+ }
36
+
37
+ /* ── Base wrapper ── */
38
+
39
+ function MonitorNodeWrapper({
40
+ id,
41
+ data,
42
+ icon,
43
+ label,
44
+ detail,
45
+ hasSource = true,
46
+ hasTarget = true,
47
+ sourceHandles,
48
+ }: {
49
+ id: string;
50
+ data: Record<string, unknown>;
51
+ icon: React.ReactNode;
52
+ label: string;
53
+ detail?: string;
54
+ hasSource?: boolean;
55
+ hasTarget?: boolean;
56
+ sourceHandles?: string[];
57
+ }) {
58
+ const execStatus = (data._execStatus as string) || 'pending';
59
+ const error = data._error as string | null;
60
+ const onNodeClick = data._onNodeClick as ((nodeId: string) => void) | undefined;
61
+ const ringCls = STATUS_RING[execStatus] || '';
62
+ const bgCls = STATUS_BG[execStatus] || STATUS_BG.pending;
63
+
64
+ return (
65
+ <div
66
+ className={`px-4 py-3 rounded-lg border border-edge ${bgCls} ${ringCls} text-content min-w-[160px] cursor-pointer transition-all hover:border-content/30`}
67
+ onClick={(e) => { e.stopPropagation(); onNodeClick?.(id); }}
68
+ >
69
+ {hasTarget && (
70
+ <Handle type="target" position={Position.Top} className="!w-3 !h-3 !bg-accent !border-2 !border-surface" />
71
+ )}
72
+ <div className="flex items-center gap-2 mb-1">
73
+ {statusIcon(execStatus)}
74
+ {icon}
75
+ <span className="text-[11px] font-semibold uppercase tracking-wider">{label}</span>
76
+ </div>
77
+ {detail && (
78
+ <div className="text-[10px] text-content-secondary truncate">{detail}</div>
79
+ )}
80
+ {error && (
81
+ <div className="mt-1 flex items-center gap-1 text-[10px] text-error truncate">
82
+ <AlertTriangle size={10} />
83
+ <span className="truncate">{error}</span>
84
+ </div>
85
+ )}
86
+ {hasSource && !sourceHandles && (
87
+ <Handle type="source" position={Position.Bottom} className="!w-3 !h-3 !bg-accent !border-2 !border-surface" />
88
+ )}
89
+ {sourceHandles && sourceHandles.map(handle => (
90
+ <Handle
91
+ key={handle}
92
+ type="source"
93
+ position={Position.Bottom}
94
+ id={handle}
95
+ className="!w-3 !h-3 !bg-accent !border-2 !border-surface"
96
+ />
97
+ ))}
98
+ </div>
99
+ );
100
+ }
101
+
102
+ /* ── Specific node types ── */
103
+
104
+ export function MonitorStartNode({ id, data }: NodeProps) {
105
+ return (
106
+ <MonitorNodeWrapper
107
+ id={id}
108
+ data={data as Record<string, unknown>}
109
+ icon={<Play size={14} className="text-accent" />}
110
+ label="Start"
111
+ hasTarget={false}
112
+ />
113
+ );
114
+ }
115
+
116
+ export function MonitorEndNode({ id, data }: NodeProps) {
117
+ const outcome = (data.outcome as string) || (data.statusType as string) || 'success';
118
+ return (
119
+ <MonitorNodeWrapper
120
+ id={id}
121
+ data={data as Record<string, unknown>}
122
+ icon={<Square size={14} className={outcome === 'success' ? 'text-accent' : outcome === 'blocked' ? 'text-amber-400' : 'text-error'} />}
123
+ label="End"
124
+ detail={outcome}
125
+ hasSource={false}
126
+ />
127
+ );
128
+ }
129
+
130
+ export function MonitorTransitionCardNode({ id, data }: NodeProps) {
131
+ const d = data as Record<string, unknown>;
132
+ return (
133
+ <MonitorNodeWrapper
134
+ id={id}
135
+ data={d}
136
+ icon={<ArrowLeftRight size={14} className="text-blue-400" />}
137
+ label="Transition Card"
138
+ detail={`${d.fromColumn || '?'} \u2192 ${d.toColumn || '?'}`}
139
+ />
140
+ );
141
+ }
142
+
143
+ export function MonitorRunAgentNode({ id, data }: NodeProps) {
144
+ const d = data as Record<string, unknown>;
145
+ return (
146
+ <MonitorNodeWrapper
147
+ id={id}
148
+ data={d}
149
+ icon={<Bot size={14} className="text-purple-400" />}
150
+ label="Run Agent"
151
+ detail={(d.agentName as string) || (d.agentId as string) || ''}
152
+ />
153
+ );
154
+ }
155
+
156
+ export function MonitorCheckCardPositionNode({ id, data }: NodeProps) {
157
+ const d = data as Record<string, unknown>;
158
+ const outputs = (d.outputs as string[]) || ['backlog', 'todo', 'in_progress', 'in_review', 'qa', 'done'];
159
+ return (
160
+ <MonitorNodeWrapper
161
+ id={id}
162
+ data={d}
163
+ icon={<MapPin size={14} className="text-cyan-400" />}
164
+ label="Check Position"
165
+ sourceHandles={outputs}
166
+ />
167
+ );
168
+ }
169
+
170
+ export function MonitorCheckCycleCountNode({ id, data }: NodeProps) {
171
+ const d = data as Record<string, unknown>;
172
+ return (
173
+ <MonitorNodeWrapper
174
+ id={id}
175
+ data={d}
176
+ icon={<RefreshCw size={14} className="text-amber-400" />}
177
+ label="Check Cycles"
178
+ detail={`max: ${d.maxCycles || '?'}`}
179
+ sourceHandles={['under_limit', 'at_limit']}
180
+ />
181
+ );
182
+ }
183
+
184
+ export function MonitorSetCardMetadataNode({ id, data }: NodeProps) {
185
+ const d = data as Record<string, unknown>;
186
+ return (
187
+ <MonitorNodeWrapper
188
+ id={id}
189
+ data={d}
190
+ icon={<Tag size={14} className="text-teal-400" />}
191
+ label="Set Metadata"
192
+ detail={`${d.key || '?'} = ${d.value || '?'}`}
193
+ />
194
+ );
195
+ }
196
+
197
+ export function MonitorGroupNode({ id, data }: NodeProps) {
198
+ const d = data as Record<string, unknown>;
199
+ return (
200
+ <MonitorNodeWrapper
201
+ id={id}
202
+ data={d}
203
+ icon={<Layers size={14} className="text-indigo-400" />}
204
+ label="Group"
205
+ detail={(d.groupName as string) || ''}
206
+ />
207
+ );
208
+ }
@@ -0,0 +1,119 @@
1
+ import { describe, it } from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import {
4
+ WORKFLOW_NODE_TYPES,
5
+ KANBAN_COLUMNS,
6
+ END_STATUS_TYPES,
7
+ NODE_PALETTE_ITEMS,
8
+ NODE_DEFAULTS,
9
+ } from './workflow_types.ts';
10
+
11
+ describe('WORKFLOW_NODE_TYPES', () => {
12
+ it('contains all eight node types including group', () => {
13
+ assert.equal(WORKFLOW_NODE_TYPES.length, 8);
14
+ assert.ok(WORKFLOW_NODE_TYPES.includes('start'));
15
+ assert.ok(WORKFLOW_NODE_TYPES.includes('end'));
16
+ assert.ok(WORKFLOW_NODE_TYPES.includes('transitionCard'));
17
+ assert.ok(WORKFLOW_NODE_TYPES.includes('runAgent'));
18
+ assert.ok(WORKFLOW_NODE_TYPES.includes('checkCardPosition'));
19
+ assert.ok(WORKFLOW_NODE_TYPES.includes('checkCycleCount'));
20
+ assert.ok(WORKFLOW_NODE_TYPES.includes('setCardMetadata'));
21
+ assert.ok(WORKFLOW_NODE_TYPES.includes('group'));
22
+ });
23
+ });
24
+
25
+ describe('KANBAN_COLUMNS', () => {
26
+ it('contains the six kanban columns in order', () => {
27
+ assert.deepEqual([...KANBAN_COLUMNS], [
28
+ 'backlog', 'todo', 'in_progress', 'in_review', 'qa', 'done',
29
+ ]);
30
+ });
31
+ });
32
+
33
+ describe('END_STATUS_TYPES', () => {
34
+ it('contains success, blocked, and failed', () => {
35
+ assert.deepEqual([...END_STATUS_TYPES], ['success', 'blocked', 'failed']);
36
+ });
37
+ });
38
+
39
+ describe('NODE_PALETTE_ITEMS', () => {
40
+ it('has entries for all non-group node types', () => {
41
+ // group is not in the palette (it's added via the Groups section)
42
+ const paletteTypes = NODE_PALETTE_ITEMS.map(item => item.type);
43
+ for (const nodeType of WORKFLOW_NODE_TYPES) {
44
+ if (nodeType === 'group') continue;
45
+ assert.ok(paletteTypes.includes(nodeType), `Missing palette item for ${nodeType}`);
46
+ }
47
+ });
48
+
49
+ it('each item has a non-empty label and description', () => {
50
+ for (const item of NODE_PALETTE_ITEMS) {
51
+ assert.ok(item.label.length > 0, `Empty label for ${item.type}`);
52
+ assert.ok(item.description.length > 0, `Empty description for ${item.type}`);
53
+ }
54
+ });
55
+ });
56
+
57
+ describe('NODE_DEFAULTS', () => {
58
+ it('has a default factory for every node type', () => {
59
+ for (const nodeType of WORKFLOW_NODE_TYPES) {
60
+ assert.ok(typeof NODE_DEFAULTS[nodeType] === 'function', `Missing default for ${nodeType}`);
61
+ }
62
+ });
63
+
64
+ it('start returns empty object', () => {
65
+ const result = NODE_DEFAULTS.start();
66
+ assert.deepEqual(result, {});
67
+ });
68
+
69
+ it('transitionCard returns default columns', () => {
70
+ const result = NODE_DEFAULTS.transitionCard();
71
+ assert.equal(result.sourceColumn, 'todo');
72
+ assert.equal(result.targetColumn, 'in_progress');
73
+ });
74
+
75
+ it('runAgent returns empty agentId and agentName', () => {
76
+ const result = NODE_DEFAULTS.runAgent();
77
+ assert.equal(result.agentId, '');
78
+ assert.equal(result.agentName, '');
79
+ });
80
+
81
+ it('checkCardPosition returns empty object', () => {
82
+ const result = NODE_DEFAULTS.checkCardPosition();
83
+ assert.deepEqual(result, {});
84
+ });
85
+
86
+ it('checkCycleCount defaults maxCount to 3', () => {
87
+ const result = NODE_DEFAULTS.checkCycleCount();
88
+ assert.equal(result.maxCount, 3);
89
+ });
90
+
91
+ it('setCardMetadata returns empty key and value', () => {
92
+ const result = NODE_DEFAULTS.setCardMetadata();
93
+ assert.equal(result.key, '');
94
+ assert.equal(result.value, '');
95
+ });
96
+
97
+ it('end defaults statusType to success', () => {
98
+ const result = NODE_DEFAULTS.end();
99
+ assert.equal(result.statusType, 'success');
100
+ });
101
+
102
+ it('group returns default group data', () => {
103
+ const result = NODE_DEFAULTS.group();
104
+ assert.equal(result.groupId, '');
105
+ assert.equal(result.groupName, '');
106
+ assert.ok(Array.isArray(result.internalNodes));
107
+ assert.ok(Array.isArray(result.internalEdges));
108
+ assert.ok(Array.isArray(result.inputHandles));
109
+ assert.ok(Array.isArray(result.outputHandles));
110
+ });
111
+
112
+ it('each factory returns a new object on each call (no shared state)', () => {
113
+ for (const nodeType of WORKFLOW_NODE_TYPES) {
114
+ const a = NODE_DEFAULTS[nodeType]();
115
+ const b = NODE_DEFAULTS[nodeType]();
116
+ assert.notEqual(a, b, `${nodeType} factory returns same object reference`);
117
+ }
118
+ });
119
+ });
@@ -0,0 +1,107 @@
1
+ import type { Node, Edge } from '@xyflow/react';
2
+
3
+ export const WORKFLOW_NODE_TYPES = [
4
+ 'start',
5
+ 'transitionCard',
6
+ 'runAgent',
7
+ 'checkCardPosition',
8
+ 'checkCycleCount',
9
+ 'setCardMetadata',
10
+ 'end',
11
+ 'group',
12
+ ] as const;
13
+
14
+ export type WorkflowNodeType = typeof WORKFLOW_NODE_TYPES[number];
15
+
16
+ export const KANBAN_COLUMNS = [
17
+ 'backlog',
18
+ 'todo',
19
+ 'in_progress',
20
+ 'in_review',
21
+ 'qa',
22
+ 'done',
23
+ ] as const;
24
+
25
+ export type KanbanColumn = typeof KANBAN_COLUMNS[number];
26
+
27
+ export const END_STATUS_TYPES = ['success', 'blocked', 'failed'] as const;
28
+ export type EndStatusType = typeof END_STATUS_TYPES[number];
29
+
30
+ export interface TransitionCardData {
31
+ sourceColumn: KanbanColumn;
32
+ targetColumn: KanbanColumn;
33
+ }
34
+
35
+ export interface RunAgentData {
36
+ agentId: string;
37
+ agentName: string;
38
+ }
39
+
40
+ export interface CheckCardPositionData {
41
+ // no config needed — outputs are per-column handles
42
+ }
43
+
44
+ export interface CheckCycleCountData {
45
+ maxCount: number;
46
+ }
47
+
48
+ export interface SetCardMetadataData {
49
+ key: string;
50
+ value: string;
51
+ }
52
+
53
+ export interface EndNodeData {
54
+ statusType: EndStatusType;
55
+ }
56
+
57
+ export interface StartNodeData {
58
+ // no config
59
+ }
60
+
61
+ export interface GroupNodeData {
62
+ groupId: string;
63
+ groupName: string;
64
+ /** The internal nodes/edges of this group instance (may diverge from saved definition) */
65
+ internalNodes: Node[];
66
+ internalEdges: Edge[];
67
+ /** Handle IDs for external connections */
68
+ inputHandles: string[];
69
+ outputHandles: string[];
70
+ }
71
+
72
+ export type WorkflowNodeData =
73
+ | ({ type: 'start' } & StartNodeData)
74
+ | ({ type: 'transitionCard' } & TransitionCardData)
75
+ | ({ type: 'runAgent' } & RunAgentData)
76
+ | ({ type: 'checkCardPosition' } & CheckCardPositionData)
77
+ | ({ type: 'checkCycleCount' } & CheckCycleCountData)
78
+ | ({ type: 'setCardMetadata' } & SetCardMetadataData)
79
+ | ({ type: 'end' } & EndNodeData)
80
+ | ({ type: 'group' } & GroupNodeData);
81
+
82
+ export interface NodePaletteItem {
83
+ type: WorkflowNodeType;
84
+ label: string;
85
+ description: string;
86
+ }
87
+
88
+ export const NODE_PALETTE_ITEMS: NodePaletteItem[] = [
89
+ { type: 'start', label: 'Start', description: 'Workflow entry point' },
90
+ { type: 'transitionCard', label: 'Transition Card', description: 'Move card between columns' },
91
+ { type: 'runAgent', label: 'Run Agent', description: 'Execute an AI agent' },
92
+ { type: 'checkCardPosition', label: 'Check Card Position', description: 'Branch by card column' },
93
+ { type: 'checkCycleCount', label: 'Check Cycle Count', description: 'Branch by iteration count' },
94
+ { type: 'setCardMetadata', label: 'Set Card Metadata', description: 'Set key/value on card' },
95
+ { type: 'end', label: 'End', description: 'Workflow exit point' },
96
+ ];
97
+
98
+ export const NODE_DEFAULTS: Record<string, () => Record<string, unknown>> = {
99
+ start: () => ({}),
100
+ transitionCard: () => ({ sourceColumn: 'todo', targetColumn: 'in_progress' }),
101
+ runAgent: () => ({ agentId: '', agentName: '' }),
102
+ checkCardPosition: () => ({}),
103
+ checkCycleCount: () => ({ maxCount: 3 }),
104
+ setCardMetadata: () => ({ key: '', value: '' }),
105
+ end: () => ({ statusType: 'success' }),
106
+ group: () => ({ groupId: '', groupName: '', internalNodes: [], internalEdges: [], inputHandles: [], outputHandles: [] }),
107
+ };
@@ -4,6 +4,7 @@
4
4
 
5
5
  export const NODE_COLORS: Record<string, string> = {
6
6
  feature: '#4dabf7',
7
+ epic: '#e599f7',
7
8
  component: '#69db7c',
8
9
  data_entity: '#ffd43b',
9
10
  decision: '#ff8787',
@@ -47,6 +48,7 @@ export const EDGE_COLORS: Record<string, string> = {
47
48
 
48
49
  export const TYPE_LABELS: Record<string, string> = {
49
50
  feature: 'FEAT',
51
+ epic: 'EPIC',
50
52
  component: 'COMP',
51
53
  data_entity: 'DATA',
52
54
  decision: 'DEC',
@@ -62,6 +64,7 @@ export const TYPE_LABELS: Record<string, string> = {
62
64
 
63
65
  export const LEGEND_LABELS: Record<string, string> = {
64
66
  feature: 'Feature',
67
+ epic: 'Epic',
65
68
  component: 'Component',
66
69
  data_entity: 'Data Entity',
67
70
  decision: 'Decision',
@@ -77,6 +80,7 @@ export const LEGEND_LABELS: Record<string, string> = {
77
80
 
78
81
  export const NODE_SHAPES: Record<string, string> = {
79
82
  feature: 'circle',
83
+ epic: 'shield',
80
84
  component: 'rounded-rect',
81
85
  data_entity: 'diamond',
82
86
  decision: 'hexagon',
@@ -157,13 +161,18 @@ export const nodeShapePath = (shape: string, r: number): string => {
157
161
  }
158
162
  return `M${pts.join(' L')} Z`;
159
163
  }
164
+ case 'shield': {
165
+ const w = r * 0.9;
166
+ const h = r * 1.2;
167
+ return `M${-w},${-h * 0.6} L${-w},${h * 0.2} Q${-w},${h * 0.7} 0,${h} Q${w},${h * 0.7} ${w},${h * 0.2} L${w},${-h * 0.6} Q${w * 0.3},${-h} ${-w * 0.3},${-h} Q${-w},${-h * 0.6} ${-w},${-h * 0.6} Z`;
168
+ }
160
169
  default:
161
170
  return `M${-r},0 A${r},${r} 0 1,1 ${r},0 A${r},${r} 0 1,1 ${-r},0 Z`;
162
171
  }
163
172
  };
164
173
 
165
174
  export const ALL_NODE_TYPES = [
166
- 'feature', 'component', 'data_entity', 'decision', 'tech_choice',
175
+ 'feature', 'epic', 'component', 'data_entity', 'decision', 'tech_choice',
167
176
  'non_functional_requirement', 'design_token', 'design_pattern',
168
177
  'user_role', 'flow', 'assumption', 'open_question',
169
178
  ];
@@ -177,16 +186,9 @@ export const COLUMNS = [
177
186
  { id: 'done', label: 'Done' },
178
187
  ];
179
188
 
180
- export const PIPELINE_STATUS_LABELS: Record<string, string> = {
181
- creating_worktree: 'Setting up...',
182
- debugging: 'Debugging',
183
- developing: 'Developing',
184
- reviewing: 'Reviewing',
185
- fixing_review: 'Fixing review',
186
- fixing_merge: 'Fixing merge',
187
- merging: 'Merging',
189
+ export const WORKFLOW_STATUS_LABELS: Record<string, string> = {
190
+ running: 'Running',
188
191
  completed: 'Completed',
189
192
  failed: 'Failed',
190
- blocked: 'Blocked',
191
- interrupted: 'Interrupted',
193
+ idle: 'Idle',
192
194
  };