@assistkick/create 1.0.1 → 1.3.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 (194) hide show
  1. package/dist/src/scaffolder.d.ts +6 -1
  2. package/dist/src/scaffolder.js +20 -9
  3. package/dist/src/scaffolder.js.map +1 -1
  4. package/package.json +3 -2
  5. package/templates/{product-system → assistkick-product-system}/CLAUDE.md +4 -4
  6. package/templates/{product-system → assistkick-product-system}/package.json +5 -5
  7. package/templates/{product-system → assistkick-product-system}/packages/backend/package.json +2 -2
  8. package/templates/{product-system → assistkick-product-system}/packages/backend/src/routes/auth.ts +1 -1
  9. package/templates/{product-system → assistkick-product-system}/packages/backend/src/routes/coherence.ts +1 -1
  10. package/templates/assistkick-product-system/packages/backend/src/routes/git.ts +231 -0
  11. package/templates/{product-system → assistkick-product-system}/packages/backend/src/routes/graph.ts +3 -3
  12. package/templates/{product-system → assistkick-product-system}/packages/backend/src/routes/kanban.ts +6 -6
  13. package/templates/assistkick-product-system/packages/backend/src/routes/pipeline.ts +88 -0
  14. package/templates/assistkick-product-system/packages/backend/src/routes/terminal.ts +82 -0
  15. package/templates/{product-system → assistkick-product-system}/packages/backend/src/server.ts +23 -10
  16. package/templates/{product-system → assistkick-product-system}/packages/backend/src/services/coherence-review.ts +4 -4
  17. package/templates/assistkick-product-system/packages/backend/src/services/github_app_service.ts +146 -0
  18. package/templates/assistkick-product-system/packages/backend/src/services/init.ts +147 -0
  19. package/templates/{product-system → assistkick-product-system}/packages/backend/src/services/invitation_service.ts +1 -1
  20. package/templates/{product-system → assistkick-product-system}/packages/backend/src/services/password_reset_service.ts +1 -1
  21. package/templates/{product-system → assistkick-product-system}/packages/backend/src/services/project_service.ts +72 -1
  22. package/templates/assistkick-product-system/packages/backend/src/services/project_workspace_service.test.ts +87 -0
  23. package/templates/assistkick-product-system/packages/backend/src/services/project_workspace_service.ts +194 -0
  24. package/templates/assistkick-product-system/packages/backend/src/services/pty_session_manager.test.ts +159 -0
  25. package/templates/{product-system → assistkick-product-system}/packages/backend/src/services/pty_session_manager.ts +114 -39
  26. package/templates/{product-system → assistkick-product-system}/packages/backend/src/services/terminal_ws_handler.ts +28 -14
  27. package/templates/{product-system → assistkick-product-system}/packages/backend/src/services/user_management_service.ts +1 -1
  28. package/templates/{product-system → assistkick-product-system}/packages/frontend/package.json +1 -1
  29. package/templates/{product-system → assistkick-product-system}/packages/frontend/src/App.tsx +1 -1
  30. package/templates/{product-system → assistkick-product-system}/packages/frontend/src/api/client.ts +151 -0
  31. package/templates/assistkick-product-system/packages/frontend/src/components/GitRepoModal.tsx +352 -0
  32. package/templates/{product-system → assistkick-product-system}/packages/frontend/src/components/KanbanView.tsx +208 -95
  33. package/templates/{product-system → assistkick-product-system}/packages/frontend/src/components/ProjectSelector.tsx +17 -1
  34. package/templates/assistkick-product-system/packages/frontend/src/components/TerminalView.tsx +333 -0
  35. package/templates/{product-system → assistkick-product-system}/packages/frontend/src/components/Toolbar.tsx +15 -13
  36. package/templates/{product-system → assistkick-product-system}/packages/frontend/src/constants/graph.ts +1 -0
  37. package/templates/{product-system → assistkick-product-system}/packages/frontend/src/hooks/useProjects.ts +4 -0
  38. package/templates/{product-system → assistkick-product-system}/packages/frontend/src/routes/dashboard.tsx +22 -4
  39. package/templates/{product-system → assistkick-product-system}/packages/frontend/src/styles/index.css +486 -38
  40. package/templates/assistkick-product-system/packages/frontend/vite.config.ts +31 -0
  41. package/templates/assistkick-product-system/packages/shared/db/migrations/0001_vengeful_wallop.sql +1 -0
  42. package/templates/assistkick-product-system/packages/shared/db/migrations/0002_greedy_excalibur.sql +4 -0
  43. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0001_snapshot.json +826 -0
  44. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0002_snapshot.json +854 -0
  45. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/_journal.json +27 -0
  46. package/templates/{product-system → assistkick-product-system}/packages/shared/db/schema.ts +5 -0
  47. package/templates/{product-system → assistkick-product-system}/packages/shared/lib/claude-service.ts +54 -1
  48. package/templates/{product-system → assistkick-product-system}/packages/shared/lib/db.ts +1 -1
  49. package/templates/{product-system → assistkick-product-system}/packages/shared/lib/git_workflow.ts +25 -0
  50. package/templates/{product-system → assistkick-product-system}/packages/shared/lib/pipeline-state-store.ts +4 -0
  51. package/templates/{product-system → assistkick-product-system}/packages/shared/lib/pipeline.ts +329 -89
  52. package/templates/assistkick-product-system/packages/shared/lib/pipeline_orchestrator.ts +186 -0
  53. package/templates/{product-system → assistkick-product-system}/packages/shared/lib/prompt_builder.ts +2 -2
  54. package/templates/{product-system → assistkick-product-system}/packages/shared/package.json +1 -1
  55. package/templates/assistkick-product-system/packages/shared/tools/db_explorer.ts +275 -0
  56. package/templates/{product-system → assistkick-product-system}/packages/shared/tools/get_kanban.ts +2 -1
  57. package/templates/{product-system → assistkick-product-system}/packages/shared/tools/move_card.ts +3 -2
  58. package/templates/{product-system → assistkick-product-system}/packages/shared/tools/update_node.ts +2 -2
  59. package/templates/{product-system → assistkick-product-system}/tests/db_sqlite_fallback.test.ts +1 -1
  60. package/templates/{product-system → assistkick-product-system}/tests/kanban.test.ts +1 -1
  61. package/templates/{product-system → assistkick-product-system}/tests/pipeline_stats_all_cards.test.ts +1 -1
  62. package/templates/{product-system → assistkick-product-system}/tests/web_terminal.test.ts +189 -150
  63. package/templates/skills/{product-bootstrap → assistkick-bootstrap}/SKILL.md +36 -28
  64. package/templates/skills/{product-code-reviewer → assistkick-code-reviewer}/SKILL.md +26 -18
  65. package/templates/skills/assistkick-db-explorer/SKILL.md +86 -0
  66. package/templates/skills/{product-debugger → assistkick-debugger}/SKILL.md +35 -27
  67. package/templates/skills/{product-developer → assistkick-developer}/SKILL.md +40 -32
  68. package/templates/skills/{product-interview → assistkick-interview}/SKILL.md +37 -29
  69. package/templates/product-system/packages/backend/src/routes/pipeline.ts +0 -41
  70. package/templates/product-system/packages/backend/src/services/init.ts +0 -80
  71. package/templates/product-system/packages/backend/src/services/pty_session_manager.test.ts +0 -88
  72. package/templates/product-system/packages/frontend/src/components/TerminalView.tsx +0 -200
  73. package/templates/product-system/packages/frontend/vite.config.ts +0 -20
  74. package/templates/product-system/packages/shared/db/migrations/meta/_journal.json +0 -13
  75. /package/templates/{product-system → assistkick-product-system}/.env.example +0 -0
  76. /package/templates/{product-system → assistkick-product-system}/packages/backend/src/middleware/auth_middleware.test.ts +0 -0
  77. /package/templates/{product-system → assistkick-product-system}/packages/backend/src/middleware/auth_middleware.ts +0 -0
  78. /package/templates/{product-system → assistkick-product-system}/packages/backend/src/routes/projects.ts +0 -0
  79. /package/templates/{product-system → assistkick-product-system}/packages/backend/src/routes/users.ts +0 -0
  80. /package/templates/{product-system → assistkick-product-system}/packages/backend/src/services/auth_service.test.ts +0 -0
  81. /package/templates/{product-system → assistkick-product-system}/packages/backend/src/services/auth_service.ts +0 -0
  82. /package/templates/{product-system → assistkick-product-system}/packages/backend/src/services/email_service.ts +0 -0
  83. /package/templates/{product-system → assistkick-product-system}/packages/backend/src/services/invitation_service.test.ts +0 -0
  84. /package/templates/{product-system → assistkick-product-system}/packages/backend/src/services/password_reset_service.test.ts +0 -0
  85. /package/templates/{product-system → assistkick-product-system}/packages/backend/src/services/project_service.test.ts +0 -0
  86. /package/templates/{product-system → assistkick-product-system}/packages/backend/src/services/user_management_service.test.ts +0 -0
  87. /package/templates/{product-system → assistkick-product-system}/packages/backend/tsconfig.json +0 -0
  88. /package/templates/{product-system → assistkick-product-system}/packages/frontend/index.html +0 -0
  89. /package/templates/{product-system → assistkick-product-system}/packages/frontend/package-lock.json +0 -0
  90. /package/templates/{product-system → assistkick-product-system}/packages/frontend/public/favicon.svg +0 -0
  91. /package/templates/{product-system → assistkick-product-system}/packages/frontend/src/api/client_projects.test.ts +0 -0
  92. /package/templates/{product-system → assistkick-product-system}/packages/frontend/src/api/client_refresh.test.ts +0 -0
  93. /package/templates/{product-system → assistkick-product-system}/packages/frontend/src/components/CoherenceView.tsx +0 -0
  94. /package/templates/{product-system → assistkick-product-system}/packages/frontend/src/components/GraphLegend.tsx +0 -0
  95. /package/templates/{product-system → assistkick-product-system}/packages/frontend/src/components/GraphSettings.tsx +0 -0
  96. /package/templates/{product-system → assistkick-product-system}/packages/frontend/src/components/GraphView.tsx +0 -0
  97. /package/templates/{product-system → assistkick-product-system}/packages/frontend/src/components/InviteUserDialog.tsx +0 -0
  98. /package/templates/{product-system → assistkick-product-system}/packages/frontend/src/components/LoginPage.tsx +0 -0
  99. /package/templates/{product-system → assistkick-product-system}/packages/frontend/src/components/QaIssueSheet.tsx +0 -0
  100. /package/templates/{product-system → assistkick-product-system}/packages/frontend/src/components/SidePanel.tsx +0 -0
  101. /package/templates/{product-system → assistkick-product-system}/packages/frontend/src/components/UsersView.tsx +0 -0
  102. /package/templates/{product-system → assistkick-product-system}/packages/frontend/src/hooks/useAuth.tsx +0 -0
  103. /package/templates/{product-system → assistkick-product-system}/packages/frontend/src/hooks/useGraph.ts +0 -0
  104. /package/templates/{product-system → assistkick-product-system}/packages/frontend/src/hooks/useKanban.ts +0 -0
  105. /package/templates/{product-system → assistkick-product-system}/packages/frontend/src/hooks/useTheme.ts +0 -0
  106. /package/templates/{product-system → assistkick-product-system}/packages/frontend/src/hooks/useToast.tsx +0 -0
  107. /package/templates/{product-system → assistkick-product-system}/packages/frontend/src/hooks/use_projects_logic.test.ts +0 -0
  108. /package/templates/{product-system → assistkick-product-system}/packages/frontend/src/main.tsx +0 -0
  109. /package/templates/{product-system → assistkick-product-system}/packages/frontend/src/pages/accept_invitation_page.tsx +0 -0
  110. /package/templates/{product-system → assistkick-product-system}/packages/frontend/src/pages/forgot_password_page.tsx +0 -0
  111. /package/templates/{product-system → assistkick-product-system}/packages/frontend/src/pages/register_page.tsx +0 -0
  112. /package/templates/{product-system → assistkick-product-system}/packages/frontend/src/pages/reset_password_page.tsx +0 -0
  113. /package/templates/{product-system → assistkick-product-system}/packages/frontend/src/routes/ProtectedRoute.tsx +0 -0
  114. /package/templates/{product-system → assistkick-product-system}/packages/frontend/src/routes/accept_invitation.tsx +0 -0
  115. /package/templates/{product-system → assistkick-product-system}/packages/frontend/src/routes/forgot_password.tsx +0 -0
  116. /package/templates/{product-system → assistkick-product-system}/packages/frontend/src/routes/login.tsx +0 -0
  117. /package/templates/{product-system → assistkick-product-system}/packages/frontend/src/routes/register.tsx +0 -0
  118. /package/templates/{product-system → assistkick-product-system}/packages/frontend/src/routes/reset_password.tsx +0 -0
  119. /package/templates/{product-system → assistkick-product-system}/packages/frontend/src/utils/auth_validation.test.ts +0 -0
  120. /package/templates/{product-system → assistkick-product-system}/packages/frontend/src/utils/auth_validation.ts +0 -0
  121. /package/templates/{product-system → assistkick-product-system}/packages/frontend/src/utils/login_validation.test.ts +0 -0
  122. /package/templates/{product-system → assistkick-product-system}/packages/frontend/src/utils/login_validation.ts +0 -0
  123. /package/templates/{product-system → assistkick-product-system}/packages/frontend/src/utils/logout.test.ts +0 -0
  124. /package/templates/{product-system → assistkick-product-system}/packages/frontend/src/utils/node_sizing.test.ts +0 -0
  125. /package/templates/{product-system → assistkick-product-system}/packages/frontend/src/utils/node_sizing.ts +0 -0
  126. /package/templates/{product-system → assistkick-product-system}/packages/frontend/src/utils/task_status.test.ts +0 -0
  127. /package/templates/{product-system → assistkick-product-system}/packages/frontend/src/utils/task_status.ts +0 -0
  128. /package/templates/{product-system → assistkick-product-system}/packages/frontend/tsconfig.json +0 -0
  129. /package/templates/{product-system → assistkick-product-system}/packages/shared/.env.example +0 -0
  130. /package/templates/{product-system → assistkick-product-system}/packages/shared/README.md +0 -0
  131. /package/templates/{product-system → assistkick-product-system}/packages/shared/db/migrate.ts +0 -0
  132. /package/templates/{product-system → assistkick-product-system}/packages/shared/db/migrations/0000_dashing_gorgon.sql +0 -0
  133. /package/templates/{product-system → assistkick-product-system}/packages/shared/db/migrations/meta/0000_snapshot.json +0 -0
  134. /package/templates/{product-system → assistkick-product-system}/packages/shared/drizzle.config.js +0 -0
  135. /package/templates/{product-system → assistkick-product-system}/packages/shared/lib/coherence.ts +0 -0
  136. /package/templates/{product-system → assistkick-product-system}/packages/shared/lib/completeness.ts +0 -0
  137. /package/templates/{product-system → assistkick-product-system}/packages/shared/lib/constants.ts +0 -0
  138. /package/templates/{product-system → assistkick-product-system}/packages/shared/lib/graph.ts +0 -0
  139. /package/templates/{product-system → assistkick-product-system}/packages/shared/lib/kanban.ts +0 -0
  140. /package/templates/{product-system → assistkick-product-system}/packages/shared/lib/markdown.ts +0 -0
  141. /package/templates/{product-system → assistkick-product-system}/packages/shared/lib/relevance_search.ts +0 -0
  142. /package/templates/{product-system → assistkick-product-system}/packages/shared/lib/session.ts +0 -0
  143. /package/templates/{product-system → assistkick-product-system}/packages/shared/lib/validator.ts +0 -0
  144. /package/templates/{product-system → assistkick-product-system}/packages/shared/lib/work_summary_parser.ts +0 -0
  145. /package/templates/{product-system → assistkick-product-system}/packages/shared/scripts/assign-project.ts +0 -0
  146. /package/templates/{product-system → assistkick-product-system}/packages/shared/tools/add_edge.ts +0 -0
  147. /package/templates/{product-system → assistkick-product-system}/packages/shared/tools/add_node.ts +0 -0
  148. /package/templates/{product-system → assistkick-product-system}/packages/shared/tools/end_session.ts +0 -0
  149. /package/templates/{product-system → assistkick-product-system}/packages/shared/tools/get_gaps.ts +0 -0
  150. /package/templates/{product-system → assistkick-product-system}/packages/shared/tools/get_node.ts +0 -0
  151. /package/templates/{product-system → assistkick-product-system}/packages/shared/tools/get_status.ts +0 -0
  152. /package/templates/{product-system → assistkick-product-system}/packages/shared/tools/migrate_to_turso.ts +0 -0
  153. /package/templates/{product-system → assistkick-product-system}/packages/shared/tools/rebuild_index.ts +0 -0
  154. /package/templates/{product-system → assistkick-product-system}/packages/shared/tools/remove_edge.ts +0 -0
  155. /package/templates/{product-system → assistkick-product-system}/packages/shared/tools/remove_node.ts +0 -0
  156. /package/templates/{product-system → assistkick-product-system}/packages/shared/tools/resolve_question.ts +0 -0
  157. /package/templates/{product-system → assistkick-product-system}/packages/shared/tools/search_nodes.ts +0 -0
  158. /package/templates/{product-system → assistkick-product-system}/packages/shared/tools/start_session.ts +0 -0
  159. /package/templates/{product-system → assistkick-product-system}/packages/shared/tsconfig.json +0 -0
  160. /package/templates/{product-system → assistkick-product-system}/pnpm-workspace.yaml +0 -0
  161. /package/templates/{product-system → assistkick-product-system}/smoke_test.ts +0 -0
  162. /package/templates/{product-system → assistkick-product-system}/tests/coherence_review.test.ts +0 -0
  163. /package/templates/{product-system → assistkick-product-system}/tests/edge_type_color_coding.test.ts +0 -0
  164. /package/templates/{product-system → assistkick-product-system}/tests/emit-tool-use-events.test.ts +0 -0
  165. /package/templates/{product-system → assistkick-product-system}/tests/feature_kind.test.ts +0 -0
  166. /package/templates/{product-system → assistkick-product-system}/tests/gap_indicators.test.ts +0 -0
  167. /package/templates/{product-system → assistkick-product-system}/tests/graceful_init.test.ts +0 -0
  168. /package/templates/{product-system → assistkick-product-system}/tests/graph_legend.test.ts +0 -0
  169. /package/templates/{product-system → assistkick-product-system}/tests/graph_settings_sheet.test.ts +0 -0
  170. /package/templates/{product-system → assistkick-product-system}/tests/hide_defined_filter.test.ts +0 -0
  171. /package/templates/{product-system → assistkick-product-system}/tests/neighborhood_focus.test.ts +0 -0
  172. /package/templates/{product-system → assistkick-product-system}/tests/node_search.test.ts +0 -0
  173. /package/templates/{product-system → assistkick-product-system}/tests/node_sizing.test.ts +0 -0
  174. /package/templates/{product-system → assistkick-product-system}/tests/node_type_toggle_filters.test.ts +0 -0
  175. /package/templates/{product-system → assistkick-product-system}/tests/node_type_visual_encoding.test.ts +0 -0
  176. /package/templates/{product-system → assistkick-product-system}/tests/pipeline-state-store.test.ts +0 -0
  177. /package/templates/{product-system → assistkick-product-system}/tests/pipeline-unit.test.ts +0 -0
  178. /package/templates/{product-system → assistkick-product-system}/tests/pipeline.test.ts +0 -0
  179. /package/templates/{product-system → assistkick-product-system}/tests/play_all.test.ts +0 -0
  180. /package/templates/{product-system → assistkick-product-system}/tests/qa_issue_sheet.test.ts +0 -0
  181. /package/templates/{product-system → assistkick-product-system}/tests/relevance_search.test.ts +0 -0
  182. /package/templates/{product-system → assistkick-product-system}/tests/search_reorder.test.ts +0 -0
  183. /package/templates/{product-system → assistkick-product-system}/tests/serve_ui.test.ts +0 -0
  184. /package/templates/{product-system → assistkick-product-system}/tests/serve_ui_drizzle.test.ts +0 -0
  185. /package/templates/{product-system → assistkick-product-system}/tests/session_context_recall.test.ts +0 -0
  186. /package/templates/{product-system → assistkick-product-system}/tests/side_panel.test.ts +0 -0
  187. /package/templates/{product-system → assistkick-product-system}/tests/spec_completeness_label.test.ts +0 -0
  188. /package/templates/{product-system → assistkick-product-system}/tests/url_routing_test.ts +0 -0
  189. /package/templates/{product-system → assistkick-product-system}/tests/user_login.test.ts +0 -0
  190. /package/templates/{product-system → assistkick-product-system}/tests/user_registration.test.ts +0 -0
  191. /package/templates/{product-system → assistkick-product-system}/tests/work_summary.test.ts +0 -0
  192. /package/templates/{product-system → assistkick-product-system}/tests/zoom_pan.test.ts +0 -0
  193. /package/templates/{product-system → assistkick-product-system}/tsconfig.json +0 -0
  194. /package/templates/skills/{product-debugger → assistkick-debugger}/references/agent-browser.md +0 -0
@@ -26,6 +26,44 @@ interface CardData {
26
26
 
27
27
  const ALL_COLUMNS = COLUMNS.map(c => c.id);
28
28
 
29
+ const STAGE_TABS = [
30
+ { key: 'in_progress', label: 'Dev' },
31
+ { key: 'in_review', label: 'Review' },
32
+ { key: 'qa', label: 'QA' },
33
+ ] as const;
34
+
35
+ const hasStageData = (stage: any): boolean => {
36
+ if (!stage) return false;
37
+ const tc = stage.toolCalls;
38
+ return (tc && tc.total > 0)
39
+ || stage.numTurns != null
40
+ || stage.costUsd != null
41
+ || stage.usage != null
42
+ || stage.stopReason != null;
43
+ };
44
+
45
+ const formatContextFill = (lastTurnUsage: any, contextWindow: number | null): string => {
46
+ if (!lastTurnUsage) return '';
47
+ const fill = (lastTurnUsage.input_tokens || 0)
48
+ + (lastTurnUsage.cache_creation_input_tokens || 0)
49
+ + (lastTurnUsage.cache_read_input_tokens || 0);
50
+ const denom = contextWindow || 200_000;
51
+ const pct = Math.round((fill / denom) * 100);
52
+ return `${pct}%`;
53
+ };
54
+
55
+ const formatCost = (costUsd: number | null): string => {
56
+ if (costUsd == null) return '';
57
+ return `$${costUsd.toFixed(4)}`;
58
+ };
59
+
60
+ const formatModel = (model: string | null): string => {
61
+ if (!model) return '';
62
+ // Shorten long model names: "claude-sonnet-4-20250514" → "sonnet-4"
63
+ const match = model.match(/(opus|sonnet|haiku)-(\d[\d.]*)/);
64
+ return match ? `${match[1]}-${match[2]}` : model;
65
+ };
66
+
29
67
  export function KanbanView({ graphData, projectId, onCardClick, onIssuesClick }: KanbanViewProps) {
30
68
  const [kanbanData, setKanbanData] = useState<any>(null);
31
69
  const [error, setError] = useState<string | null>(null);
@@ -35,10 +73,11 @@ export function KanbanView({ graphData, projectId, onCardClick, onIssuesClick }:
35
73
  const [playAllRunning, setPlayAllRunning] = useState(false);
36
74
  const [playAllCurrentFeature, setPlayAllCurrentFeature] = useState<string | null>(null);
37
75
  const [copiedId, setCopiedId] = useState<string | null>(null);
76
+ const [activeStageTab, setActiveStageTab] = useState<Record<string, string>>({});
38
77
  const { showToast } = useToast();
39
78
 
40
79
  const pollersRef = useRef<Map<string, ReturnType<typeof setInterval>>>(new Map());
41
- const playAllAbortedRef = useRef(false);
80
+ const orchestratorPollerRef = useRef<ReturnType<typeof setInterval> | null>(null);
42
81
  const kanbanDataRef = useRef(kanbanData);
43
82
  kanbanDataRef.current = kanbanData;
44
83
 
@@ -62,6 +101,10 @@ export function KanbanView({ graphData, projectId, onCardClick, onIssuesClick }:
62
101
  clearInterval(intervalId);
63
102
  }
64
103
  pollersRef.current.clear();
104
+ if (orchestratorPollerRef.current) {
105
+ clearInterval(orchestratorPollerRef.current);
106
+ orchestratorPollerRef.current = null;
107
+ }
65
108
  };
66
109
  }, [fetchKanban]);
67
110
 
@@ -204,6 +247,17 @@ export function KanbanView({ graphData, projectId, onCardClick, onIssuesClick }:
204
247
  }
205
248
  };
206
249
 
250
+ const handleResumePipeline = async (featureId: string) => {
251
+ try {
252
+ await apiClient.resumePipeline(featureId);
253
+ startPipelinePolling(featureId);
254
+ await fetchKanban();
255
+ } catch (err: any) {
256
+ console.error('Failed to resume pipeline:', err);
257
+ showToast(err?.message || 'Failed to resume pipeline');
258
+ }
259
+ };
260
+
207
261
  const handleUnblock = async (featureId: string) => {
208
262
  try {
209
263
  await apiClient.unblockCard(featureId);
@@ -222,95 +276,71 @@ export function KanbanView({ graphData, projectId, onCardClick, onIssuesClick }:
222
276
  } catch { /* ignore */ }
223
277
  };
224
278
 
225
- // Play All
279
+ // Orchestrator status polling
280
+ const startOrchestratorPolling = useCallback(() => {
281
+ if (orchestratorPollerRef.current) return;
282
+ orchestratorPollerRef.current = setInterval(async () => {
283
+ try {
284
+ const status = await apiClient.getOrchestratorStatus();
285
+ setPlayAllRunning(status.active);
286
+ setPlayAllCurrentFeature(status.currentFeatureId);
287
+ if (status.active) {
288
+ await fetchKanban();
289
+ } else {
290
+ // Orchestrator stopped — clean up poller
291
+ if (orchestratorPollerRef.current) {
292
+ clearInterval(orchestratorPollerRef.current);
293
+ orchestratorPollerRef.current = null;
294
+ }
295
+ await fetchKanban();
296
+ }
297
+ } catch {
298
+ if (orchestratorPollerRef.current) {
299
+ clearInterval(orchestratorPollerRef.current);
300
+ orchestratorPollerRef.current = null;
301
+ }
302
+ setPlayAllRunning(false);
303
+ setPlayAllCurrentFeature(null);
304
+ }
305
+ }, 5000);
306
+ }, [fetchKanban]);
307
+
308
+ // Check orchestrator status on mount to sync with server state
309
+ useEffect(() => {
310
+ apiClient.getOrchestratorStatus().then(status => {
311
+ setPlayAllRunning(status.active);
312
+ setPlayAllCurrentFeature(status.currentFeatureId);
313
+ if (status.active) {
314
+ startOrchestratorPolling();
315
+ }
316
+ }).catch(() => { /* ignore */ });
317
+
318
+ return () => {
319
+ if (orchestratorPollerRef.current) {
320
+ clearInterval(orchestratorPollerRef.current);
321
+ orchestratorPollerRef.current = null;
322
+ }
323
+ };
324
+ }, [startOrchestratorPolling]);
325
+
326
+ // Play All — thin wrapper around backend orchestrator
226
327
  const startPlayAll = async () => {
227
328
  if (playAllRunning) return;
228
- setPlayAllRunning(true);
229
- playAllAbortedRef.current = false;
230
- setPlayAllCurrentFeature(null);
231
-
232
329
  try {
233
- await playAllLoop();
234
- } finally {
235
- setPlayAllRunning(false);
330
+ await apiClient.startPlayAll(projectId ?? undefined);
331
+ setPlayAllRunning(true);
236
332
  setPlayAllCurrentFeature(null);
333
+ startOrchestratorPolling();
334
+ } catch (err: any) {
335
+ showToast(err?.message || 'Failed to start Play All');
237
336
  }
238
337
  };
239
338
 
240
- const stopPlayAll = () => {
241
- playAllAbortedRef.current = true;
242
- };
243
-
244
- const playAllLoop = async () => {
245
- while (!playAllAbortedRef.current) {
246
- let freshKanban: any;
247
- let freshGraph: any;
248
- try {
249
- freshKanban = await apiClient.fetchKanban(projectId ?? undefined);
250
- freshGraph = await apiClient.fetchGraph(projectId ?? undefined);
251
- setKanbanData(freshKanban);
252
- } catch { break; }
253
-
254
- const featureNodes = new Map<string, any>();
255
- freshGraph.nodes
256
- .filter((n: any) => n.type === 'feature')
257
- .forEach((n: any) => featureNodes.set(n.id, n));
258
-
259
- const todoCards = Object.entries(freshKanban)
260
- .filter(([id, entry]: [string, any]) => entry.column === 'todo' && featureNodes.has(id) && !entry.dev_blocked)
261
- .map(([id, entry]: [string, any]) => ({
262
- id,
263
- completeness: featureNodes.get(id).completeness || 0,
264
- }))
265
- .sort((a, b) => b.completeness - a.completeness);
266
-
267
- if (todoCards.length === 0) break;
268
-
269
- let processed = false;
270
- for (const card of todoCards) {
271
- if (playAllAbortedRef.current) return;
272
-
273
- // Check deps
274
- const deps = freshGraph.edges
275
- .filter((e: any) => e.from === card.id && e.relation === 'depends_on')
276
- .map((e: any) => e.to)
277
- .filter((depId: string) => freshGraph.nodes.some((n: any) => n.id === depId && n.type === 'feature'));
278
- const blocked = deps.some((depId: string) => !freshKanban[depId] || freshKanban[depId].column !== 'done');
279
- if (blocked) continue;
280
-
281
- setPlayAllCurrentFeature(card.id);
282
-
283
- try {
284
- await apiClient.startPipeline(card.id);
285
- } catch (err: any) {
286
- console.error(`Play All: failed to start pipeline for ${card.id}:`, err);
287
- showToast(err?.message || `Failed to start pipeline for ${card.id}`);
288
- break;
289
- }
290
-
291
- // Wait for pipeline completion
292
- await new Promise<void>((resolve) => {
293
- const poll = async () => {
294
- if (playAllAbortedRef.current) { resolve(); return; }
295
- try {
296
- const status = await apiClient.getPipelineStatus(card.id);
297
- setPipelineStatuses(prev => ({ ...prev, [card.id]: status }));
298
- if (['idle', 'completed', 'blocked', 'failed', 'interrupted'].includes(status.status)) {
299
- resolve();
300
- return;
301
- }
302
- } catch { resolve(); return; }
303
- setTimeout(poll, 5000);
304
- };
305
- poll();
306
- });
307
-
308
- if (playAllAbortedRef.current) return;
309
- processed = true;
310
- break;
311
- }
312
-
313
- if (!processed) break;
339
+ const stopPlayAll = async () => {
340
+ try {
341
+ await apiClient.stopPlayAll();
342
+ } catch (err: any) {
343
+ showToast(err?.message || 'Failed to stop Play All');
314
344
  }
315
345
  };
316
346
 
@@ -356,6 +386,10 @@ export function KanbanView({ graphData, projectId, onCardClick, onIssuesClick }:
356
386
  const pStatus = pipelineStatuses[card.id];
357
387
  const isActive = pStatus && !['idle', 'completed', 'blocked', 'failed', 'interrupted'].includes(pStatus.status);
358
388
  const isTerminal = pStatus && ['completed', 'failed', 'blocked', 'interrupted'].includes(pStatus.status);
389
+ const pipelineStatusValue = pStatus?.status ?? 'idle';
390
+ const showResumeBtn = ['in_progress', 'in_review'].includes(card.column)
391
+ && !isActive
392
+ && ['interrupted', 'failed', 'idle'].includes(pipelineStatusValue);
359
393
 
360
394
  return (
361
395
  <div
@@ -400,6 +434,15 @@ export function KanbanView({ graphData, projectId, onCardClick, onIssuesClick }:
400
434
  {'\u25B6'}
401
435
  </button>
402
436
  )}
437
+ {showResumeBtn && (
438
+ <button
439
+ className="kanban-play-btn kanban-resume-btn"
440
+ title="Resume pipeline from last completed step"
441
+ onClick={(e) => { e.stopPropagation(); handleResumePipeline(card.id); }}
442
+ >
443
+ {'\u25B6'}
444
+ </button>
445
+ )}
403
446
  {card.devBlocked && (
404
447
  <span className="kanban-blocked-badge">Blocked</span>
405
448
  )}
@@ -422,18 +465,88 @@ export function KanbanView({ graphData, projectId, onCardClick, onIssuesClick }:
422
465
  </div>
423
466
  )}
424
467
 
425
- {pStatus?.toolCalls?.total > 0 && (
426
- <div className="kanban-tool-calls" style={{ display: 'flex' }}>
427
- {[
428
- { label: 'Write', count: pStatus.toolCalls.write },
429
- { label: 'Edit', count: pStatus.toolCalls.edit },
430
- { label: 'Read', count: pStatus.toolCalls.read },
431
- { label: 'Bash', count: pStatus.toolCalls.bash },
432
- ].filter(i => i.count > 0).map(i => (
433
- <span key={i.label} className="kanban-tool-badge">{i.label}: {i.count}</span>
434
- ))}
435
- </div>
436
- )}
468
+ {(() => {
469
+ const ss = pStatus?.stageStats;
470
+ const availableTabs = ss
471
+ ? STAGE_TABS.filter(t => hasStageData(ss[t.key]))
472
+ : [];
473
+ // Fall back to flat toolCalls for old data without stageStats
474
+ if (availableTabs.length === 0 && pStatus?.toolCalls?.total > 0) {
475
+ return (
476
+ <div className="kanban-tool-calls">
477
+ {[
478
+ { label: 'Write', count: pStatus.toolCalls.write },
479
+ { label: 'Edit', count: pStatus.toolCalls.edit },
480
+ { label: 'Read', count: pStatus.toolCalls.read },
481
+ { label: 'Bash', count: pStatus.toolCalls.bash },
482
+ ].filter(i => i.count > 0).map(i => (
483
+ <span key={i.label} className="kanban-tool-badge">{i.label}: {i.count}</span>
484
+ ))}
485
+ </div>
486
+ );
487
+ }
488
+ if (availableTabs.length === 0) return null;
489
+
490
+ const currentTab = activeStageTab[card.id] || availableTabs[availableTabs.length - 1].key;
491
+ const stage = ss[currentTab];
492
+ const tc = stage?.toolCalls;
493
+
494
+ return (
495
+ <div className="kanban-stage-stats" onClick={(e) => e.stopPropagation()}>
496
+ <div className="kanban-stage-tabs">
497
+ {availableTabs.map(t => (
498
+ <button
499
+ key={t.key}
500
+ className={`kanban-stage-tab${currentTab === t.key ? ' active' : ''}`}
501
+ onClick={(e) => {
502
+ e.stopPropagation();
503
+ setActiveStageTab(prev => ({ ...prev, [card.id]: t.key }));
504
+ }}
505
+ >
506
+ {t.label}
507
+ </button>
508
+ ))}
509
+ </div>
510
+ <div className="kanban-stage-body">
511
+ {tc && tc.total > 0 && (
512
+ <div className="kanban-tool-calls">
513
+ {[
514
+ { label: 'Write', count: tc.write },
515
+ { label: 'Edit', count: tc.edit },
516
+ { label: 'Read', count: tc.read },
517
+ { label: 'Bash', count: tc.bash },
518
+ ].filter(i => i.count > 0).map(i => (
519
+ <span key={i.label} className="kanban-tool-badge">{i.label}: {i.count}</span>
520
+ ))}
521
+ </div>
522
+ )}
523
+ <div className="kanban-stage-meta">
524
+ {stage?.lastTurnUsage && (
525
+ <span className="kanban-stage-meta-item" title="Peak context window utilization">
526
+ Ctx: {formatContextFill(stage.lastTurnUsage, stage.contextWindow)}
527
+ </span>
528
+ )}
529
+ {stage?.numTurns != null && (
530
+ <span className="kanban-stage-meta-item" title="Agentic turns">
531
+ {stage.numTurns} turns
532
+ </span>
533
+ )}
534
+ {stage?.costUsd != null && (
535
+ <span className="kanban-stage-meta-item" title={`Model: ${stage.model || 'unknown'}`}>
536
+ {formatCost(stage.costUsd)}
537
+ {stage.model && <span className="kanban-stage-model"> {formatModel(stage.model)}</span>}
538
+ </span>
539
+ )}
540
+ {stage?.stopReason && (
541
+ <span className="kanban-stage-meta-item kanban-stop-reason" title="Stop reason">
542
+ {stage.stopReason}
543
+ </span>
544
+ )}
545
+ </div>
546
+ </div>
547
+ </div>
548
+ );
549
+ })()}
437
550
 
438
551
  {card.devBlocked && (
439
552
  <button
@@ -8,10 +8,11 @@ interface ProjectSelectorProps {
8
8
  onCreate: (name: string) => Promise<any>;
9
9
  onRename: (id: string, name: string) => Promise<void>;
10
10
  onArchive: (id: string) => Promise<void>;
11
+ onOpenGitModal?: (project: Project) => void;
11
12
  }
12
13
 
13
14
  export function ProjectSelector({
14
- projects, selectedProjectId, onSelect, onCreate, onRename, onArchive,
15
+ projects, selectedProjectId, onSelect, onCreate, onRename, onArchive, onOpenGitModal,
15
16
  }: ProjectSelectorProps) {
16
17
  const [open, setOpen] = useState(false);
17
18
  const [creating, setCreating] = useState(false);
@@ -123,6 +124,21 @@ export function ProjectSelector({
123
124
  {project.name}
124
125
  </button>
125
126
  <div className="project-selector-item-actions">
127
+ {onOpenGitModal && (
128
+ <button
129
+ className={`project-selector-action-btn project-selector-git-btn${project.repoUrl ? ' connected' : ''}`}
130
+ title="Git Repository"
131
+ onClick={(e) => {
132
+ e.stopPropagation();
133
+ setOpen(false);
134
+ onOpenGitModal(project);
135
+ }}
136
+ >
137
+ <svg viewBox="0 0 16 16" width="14" height="14" fill="currentColor">
138
+ <path d="M8 0c4.42 0 8 3.58 8 8a8.013 8.013 0 0 1-5.45 7.59c-.4.08-.55-.17-.55-.38 0-.27.01-1.13.01-2.2 0-.75-.25-1.23-.54-1.48 1.78-.2 3.65-.88 3.65-3.95 0-.88-.31-1.59-.82-2.15.08-.2.36-1.02-.08-2.12 0 0-.67-.22-2.2.82-.64-.18-1.32-.27-2-.27-.68 0-1.36.09-2 .27-1.53-1.03-2.2-.82-2.2-.82-.44 1.1-.16 1.92-.08 2.12-.51.56-.82 1.28-.82 2.15 0 3.06 1.86 3.75 3.64 3.95-.23.2-.44.55-.51 1.07-.46.21-1.61.55-2.33-.66-.15-.24-.6-.83-1.23-.82-.67.01-.27.38.01.53.34.19.73.9.82 1.13.16.45.68 1.31 2.69.94 0 .67.01 1.3.01 1.49 0 .21-.15.45-.55.38A7.995 7.995 0 0 1 0 8c0-4.42 3.58-8 8-8Z" />
139
+ </svg>
140
+ </button>
141
+ )}
126
142
  <button
127
143
  className="project-selector-action-btn"
128
144
  title="Rename"