@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.
- package/package.json +2 -2
- package/templates/assistkick-product-system/.env.example +1 -0
- package/templates/assistkick-product-system/local.db +0 -0
- package/templates/assistkick-product-system/package.json +4 -2
- package/templates/assistkick-product-system/packages/backend/package.json +2 -0
- package/templates/assistkick-product-system/packages/backend/src/routes/agents.ts +165 -0
- package/templates/assistkick-product-system/packages/backend/src/routes/files.test.ts +358 -0
- package/templates/assistkick-product-system/packages/backend/src/routes/files.ts +356 -0
- package/templates/assistkick-product-system/packages/backend/src/routes/git.ts +96 -1
- package/templates/assistkick-product-system/packages/backend/src/routes/graph.ts +1 -0
- package/templates/assistkick-product-system/packages/backend/src/routes/kanban.ts +43 -4
- package/templates/assistkick-product-system/packages/backend/src/routes/pipeline.ts +200 -84
- package/templates/assistkick-product-system/packages/backend/src/routes/projects.ts +6 -3
- package/templates/assistkick-product-system/packages/backend/src/routes/terminal.ts +53 -17
- package/templates/assistkick-product-system/packages/backend/src/routes/video.ts +218 -0
- package/templates/assistkick-product-system/packages/backend/src/routes/workflow_groups.ts +119 -0
- package/templates/assistkick-product-system/packages/backend/src/routes/workflows.ts +154 -0
- package/templates/assistkick-product-system/packages/backend/src/server.ts +81 -9
- package/templates/assistkick-product-system/packages/backend/src/services/agent_service.test.ts +489 -0
- package/templates/assistkick-product-system/packages/backend/src/services/agent_service.ts +416 -0
- package/templates/assistkick-product-system/packages/backend/src/services/bundle_service.test.ts +189 -0
- package/templates/assistkick-product-system/packages/backend/src/services/bundle_service.ts +182 -0
- package/templates/assistkick-product-system/packages/backend/src/services/init.ts +28 -78
- package/templates/assistkick-product-system/packages/backend/src/services/project_service.test.ts +16 -0
- package/templates/assistkick-product-system/packages/backend/src/services/project_service.ts +73 -2
- package/templates/assistkick-product-system/packages/backend/src/services/project_workspace_service.test.ts +4 -4
- package/templates/assistkick-product-system/packages/backend/src/services/project_workspace_service.ts +87 -11
- package/templates/assistkick-product-system/packages/backend/src/services/pty_session_manager.test.ts +210 -69
- package/templates/assistkick-product-system/packages/backend/src/services/pty_session_manager.ts +210 -215
- package/templates/assistkick-product-system/packages/backend/src/services/ssh_key_service.test.ts +162 -0
- package/templates/assistkick-product-system/packages/backend/src/services/ssh_key_service.ts +148 -0
- package/templates/assistkick-product-system/packages/backend/src/services/terminal_ws_handler.ts +11 -5
- package/templates/assistkick-product-system/packages/backend/src/services/tts_service.test.ts +64 -0
- package/templates/assistkick-product-system/packages/backend/src/services/tts_service.ts +134 -0
- package/templates/assistkick-product-system/packages/backend/src/services/video_render_service.test.ts +256 -0
- package/templates/assistkick-product-system/packages/backend/src/services/video_render_service.ts +258 -0
- package/templates/assistkick-product-system/packages/backend/src/services/workflow_group_service.ts +106 -0
- package/templates/assistkick-product-system/packages/backend/src/services/workflow_service.test.ts +275 -0
- package/templates/assistkick-product-system/packages/backend/src/services/workflow_service.ts +222 -0
- package/templates/assistkick-product-system/packages/frontend/index.html +3 -0
- package/templates/assistkick-product-system/packages/frontend/package-lock.json +800 -11
- package/templates/assistkick-product-system/packages/frontend/package.json +11 -1
- package/templates/assistkick-product-system/packages/frontend/src/App.tsx +24 -7
- package/templates/assistkick-product-system/packages/frontend/src/api/client.ts +456 -16
- package/templates/assistkick-product-system/packages/frontend/src/api/client_files.test.ts +172 -0
- package/templates/assistkick-product-system/packages/frontend/src/api/client_video.test.ts +238 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/AgentsView.tsx +307 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/CoherenceView.tsx +82 -66
- package/templates/assistkick-product-system/packages/frontend/src/components/CompositionPlaceholder.tsx +97 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/DesignSystemView.tsx +383 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/EditorTabBar.tsx +57 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/FileTree.tsx +313 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/FileTreeContextMenu.tsx +61 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/FileTreeInlineInput.tsx +73 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/FilesView.tsx +404 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/GitRepoModal.tsx +193 -64
- package/templates/assistkick-product-system/packages/frontend/src/components/GraphLegend.tsx +71 -73
- package/templates/assistkick-product-system/packages/frontend/src/components/GraphSettings.tsx +8 -8
- package/templates/assistkick-product-system/packages/frontend/src/components/GraphView.tsx +1 -1
- package/templates/assistkick-product-system/packages/frontend/src/components/InviteUserDialog.tsx +15 -11
- package/templates/assistkick-product-system/packages/frontend/src/components/KanbanView.tsx +226 -291
- package/templates/assistkick-product-system/packages/frontend/src/components/LoginPage.tsx +14 -14
- package/templates/assistkick-product-system/packages/frontend/src/components/ProjectSelector.tsx +54 -33
- package/templates/assistkick-product-system/packages/frontend/src/components/QaIssueSheet.tsx +40 -66
- package/templates/assistkick-product-system/packages/frontend/src/components/SidePanel.tsx +55 -115
- package/templates/assistkick-product-system/packages/frontend/src/components/TerminalView.tsx +121 -52
- package/templates/assistkick-product-system/packages/frontend/src/components/Toolbar.tsx +155 -77
- package/templates/assistkick-product-system/packages/frontend/src/components/UsersView.tsx +52 -52
- package/templates/assistkick-product-system/packages/frontend/src/components/VideoGallery.tsx +313 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/VideographyView.tsx +250 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/WorkflowsView.tsx +474 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/ds/AccentBorderList.tsx +53 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/ds/Button.tsx +87 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/ds/ButtonGroup.tsx +29 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/ds/ButtonShowcase.tsx +221 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/ds/CardGlass.tsx +141 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/ds/CompletionRing.tsx +30 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/ds/ContentCard.tsx +34 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/ds/IconButton.tsx +74 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/ds/KanbanCard.tsx +270 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/ds/KanbanCardShowcase.tsx +37 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/ds/Kbd.tsx +11 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/ds/KindBadge.tsx +21 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/ds/NavBarSidekick.tsx +207 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/ds/SidePanelShowcase.tsx +370 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/ds/SideSheet.tsx +64 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/ds/StatusDot.tsx +18 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/workflow/CheckCardPositionNode.tsx +36 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/workflow/CheckCycleCountNode.tsx +60 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/workflow/EndNode.tsx +42 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/workflow/GroupNode.tsx +189 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/workflow/NodePalette.tsx +123 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/workflow/RunAgentNode.tsx +51 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/workflow/SetCardMetadataNode.tsx +53 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/workflow/StartNode.tsx +18 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/workflow/TransitionCardNode.tsx +59 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/workflow/WorkflowCanvas.tsx +335 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/workflow/WorkflowMonitorModal.tsx +634 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/workflow/autoLayout.ts +103 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/workflow/edgeColors.ts +35 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/workflow/monitor_nodes.tsx +208 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/workflow/workflow_types.test.ts +119 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/workflow/workflow_types.ts +107 -0
- package/templates/assistkick-product-system/packages/frontend/src/constants/graph.ts +13 -11
- package/templates/assistkick-product-system/packages/frontend/src/hooks/useAutoSave.ts +75 -0
- package/templates/assistkick-product-system/packages/frontend/src/hooks/useGraph.ts +6 -21
- package/templates/assistkick-product-system/packages/frontend/src/hooks/useProjects.ts +15 -80
- package/templates/assistkick-product-system/packages/frontend/src/hooks/useToast.tsx +16 -3
- package/templates/assistkick-product-system/packages/frontend/src/pages/accept_invitation_page.tsx +30 -27
- package/templates/assistkick-product-system/packages/frontend/src/pages/forgot_password_page.tsx +18 -15
- package/templates/assistkick-product-system/packages/frontend/src/pages/register_page.tsx +21 -18
- package/templates/assistkick-product-system/packages/frontend/src/pages/reset_password_page.tsx +28 -25
- package/templates/assistkick-product-system/packages/frontend/src/routes/AgentsRoute.tsx +6 -0
- package/templates/assistkick-product-system/packages/frontend/src/routes/CoherenceRoute.tsx +19 -0
- package/templates/assistkick-product-system/packages/frontend/src/routes/DashboardLayout.tsx +54 -0
- package/templates/assistkick-product-system/packages/frontend/src/routes/DesignSystemRoute.tsx +6 -0
- package/templates/assistkick-product-system/packages/frontend/src/routes/FilesRoute.tsx +13 -0
- package/templates/assistkick-product-system/packages/frontend/src/routes/GraphRoute.tsx +93 -0
- package/templates/assistkick-product-system/packages/frontend/src/routes/KanbanRoute.tsx +30 -0
- package/templates/assistkick-product-system/packages/frontend/src/routes/TerminalRoute.tsx +9 -0
- package/templates/assistkick-product-system/packages/frontend/src/routes/UsersRoute.tsx +6 -0
- package/templates/assistkick-product-system/packages/frontend/src/routes/VideographyRoute.tsx +13 -0
- package/templates/assistkick-product-system/packages/frontend/src/routes/WorkflowsRoute.tsx +6 -0
- package/templates/assistkick-product-system/packages/frontend/src/stores/useGitModalStore.ts +14 -0
- package/templates/assistkick-product-system/packages/frontend/src/stores/useGraphStore.ts +36 -0
- package/templates/assistkick-product-system/packages/frontend/src/stores/useGraphUIStore.ts +25 -0
- package/templates/assistkick-product-system/packages/frontend/src/stores/useProjectStore.ts +90 -0
- package/templates/assistkick-product-system/packages/frontend/src/stores/useQaSheetStore.ts +27 -0
- package/templates/assistkick-product-system/packages/frontend/src/stores/useSidePanelStore.ts +76 -0
- package/templates/assistkick-product-system/packages/frontend/src/styles/index.css +336 -3632
- package/templates/assistkick-product-system/packages/frontend/src/utils/auto_save_service.test.ts +167 -0
- package/templates/assistkick-product-system/packages/frontend/src/utils/auto_save_service.ts +101 -0
- package/templates/assistkick-product-system/packages/frontend/src/utils/composition_matcher.test.ts +42 -0
- package/templates/assistkick-product-system/packages/frontend/src/utils/composition_matcher.ts +17 -0
- package/templates/assistkick-product-system/packages/frontend/src/utils/file_utils.test.ts +145 -0
- package/templates/assistkick-product-system/packages/frontend/src/utils/file_utils.ts +42 -0
- package/templates/assistkick-product-system/packages/frontend/src/utils/task_status.test.ts +4 -10
- package/templates/assistkick-product-system/packages/frontend/src/utils/task_status.ts +19 -1
- package/templates/assistkick-product-system/packages/frontend/vite.config.ts +7 -1
- package/templates/assistkick-product-system/packages/shared/db/local.db +0 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/0004_tidy_matthew_murdock.sql +9 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/0005_mysterious_falcon.sql +692 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/0006_next_venom.sql +9 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/0007_deep_barracuda.sql +39 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/0008_puzzling_hannibal_king.sql +1 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/0009_amused_beast.sql +8 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/0010_spotty_moira_mactaggert.sql +9 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/0011_goofy_snowbird.sql +3 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/0011_supreme_doctor_octopus.sql +3 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/0013_reflective_prowler.sql +15 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0004_snapshot.json +921 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0005_snapshot.json +1042 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0006_snapshot.json +1101 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0007_snapshot.json +1336 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0008_snapshot.json +1275 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0009_snapshot.json +1327 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0010_snapshot.json +1393 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0011_snapshot.json +1436 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0013_snapshot.json +1538 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/meta/_journal.json +70 -0
- package/templates/assistkick-product-system/packages/shared/db/schema.ts +113 -0
- package/templates/assistkick-product-system/packages/shared/lib/claude-service.ts +32 -7
- package/templates/assistkick-product-system/packages/shared/lib/constants.ts +9 -0
- package/templates/assistkick-product-system/packages/shared/lib/git_workflow.ts +12 -4
- package/templates/assistkick-product-system/packages/shared/lib/graph.ts +16 -5
- package/templates/assistkick-product-system/packages/shared/lib/workflow_engine.test.ts +1753 -0
- package/templates/assistkick-product-system/packages/shared/lib/workflow_engine.ts +1281 -0
- package/templates/assistkick-product-system/packages/shared/lib/workflow_orchestrator.ts +211 -0
- package/templates/assistkick-product-system/packages/shared/tools/add_node.test.ts +43 -0
- package/templates/assistkick-product-system/packages/shared/tools/add_node.ts +13 -2
- package/templates/assistkick-product-system/packages/shared/tools/get_kanban.ts +1 -1
- package/templates/assistkick-product-system/packages/shared/tools/migrate_epics.test.ts +226 -0
- package/templates/assistkick-product-system/packages/shared/tools/migrate_epics.ts +251 -0
- package/templates/assistkick-product-system/packages/shared/tools/update_node.ts +2 -2
- package/templates/assistkick-product-system/packages/shared/utils/hello_workflow.test.ts +10 -0
- package/templates/assistkick-product-system/packages/shared/utils/hello_workflow.ts +6 -0
- package/templates/assistkick-product-system/packages/video/Root.tsx +85 -0
- package/templates/assistkick-product-system/packages/video/components/email_scene.tsx +231 -0
- package/templates/assistkick-product-system/packages/video/components/outro_scene.tsx +153 -0
- package/templates/assistkick-product-system/packages/video/components/part_divider.tsx +90 -0
- package/templates/assistkick-product-system/packages/video/components/scene.tsx +226 -0
- package/templates/assistkick-product-system/packages/video/components/theme.ts +22 -0
- package/templates/assistkick-product-system/packages/video/components/title_scene.tsx +169 -0
- package/templates/assistkick-product-system/packages/video/components/video_split_layout.tsx +84 -0
- package/templates/assistkick-product-system/packages/video/compositions/.gitkeep +0 -0
- package/templates/assistkick-product-system/packages/video/index.ts +4 -0
- package/templates/assistkick-product-system/packages/video/package.json +28 -0
- package/templates/assistkick-product-system/packages/video/remotion.config.ts +11 -0
- package/templates/assistkick-product-system/packages/video/scripts/process_script.test.ts +326 -0
- package/templates/assistkick-product-system/packages/video/scripts/process_script.ts +630 -0
- package/templates/assistkick-product-system/packages/video/style.css +1 -0
- package/templates/assistkick-product-system/packages/video/tsconfig.json +18 -0
- package/templates/assistkick-product-system/tests/graph_legend.test.ts +2 -1
- package/templates/assistkick-product-system/tests/video_render_service.test.ts +179 -0
- package/templates/assistkick-product-system/tests/web_terminal.test.ts +219 -455
- package/templates/assistkick-product-system/tests/workflow_integration.test.ts +341 -0
- package/templates/skills/assistkick-bootstrap/SKILL.md +3 -3
- package/templates/skills/assistkick-code-reviewer/SKILL.md +2 -2
- package/templates/skills/assistkick-debugger/SKILL.md +2 -2
- package/templates/skills/assistkick-developer/SKILL.md +6 -3
- package/templates/skills/assistkick-developer/references/react_development_guidelines.md +225 -0
- package/templates/skills/assistkick-interview/SKILL.md +2 -2
- package/templates/skills/product-system/graph.json +1890 -0
- package/templates/skills/product-system/kanban.json +304 -0
- package/templates/skills/product-system/nodes/comp_001.md +56 -0
- package/templates/skills/product-system/nodes/comp_002.md +57 -0
- package/templates/skills/product-system/nodes/data_001.md +51 -0
- package/templates/skills/product-system/nodes/data_002.md +40 -0
- package/templates/skills/product-system/nodes/data_004.md +38 -0
- package/templates/skills/product-system/nodes/dec_001.md +34 -0
- package/templates/skills/product-system/nodes/dec_016.md +32 -0
- package/templates/skills/product-system/nodes/feat_008.md +30 -0
- package/templates/skills/video-composition-agent/SKILL.md +232 -0
- package/templates/skills/video-script-writer/SKILL.md +136 -0
package/templates/assistkick-product-system/packages/frontend/src/components/workflow/autoLayout.ts
ADDED
|
@@ -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
|
+
}
|
package/templates/assistkick-product-system/packages/frontend/src/components/workflow/edgeColors.ts
ADDED
|
@@ -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
|
|
181
|
-
|
|
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
|
-
|
|
191
|
-
interrupted: 'Interrupted',
|
|
193
|
+
idle: 'Idle',
|
|
192
194
|
};
|