@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
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Terminal session routes — REST API for managing named PTY sessions.
3
+ * GET /api/terminal/sessions — list all active sessions
4
+ * POST /api/terminal/sessions — create a new named session
5
+ * DELETE /api/terminal/sessions/:id — kill and remove a session
6
+ *
7
+ * Admin-only: all routes require admin role.
8
+ */
9
+
10
+ import { Router } from 'express';
11
+ import type { Request, Response } from 'express';
12
+ import type { PtySessionManager } from '../services/pty_session_manager.js';
13
+
14
+ interface TerminalRoutesDeps {
15
+ ptyManager: PtySessionManager;
16
+ log: (tag: string, ...args: unknown[]) => void;
17
+ }
18
+
19
+ const requireAdmin = (req: Request, res: Response, next: () => void): void => {
20
+ const user = (req as any).user;
21
+ if (!user || user.role !== 'admin') {
22
+ res.status(403).json({ error: 'Admin privileges required' });
23
+ return;
24
+ }
25
+ next();
26
+ };
27
+
28
+ export const createTerminalRoutes = ({ ptyManager, log }: TerminalRoutesDeps): Router => {
29
+ const router: Router = Router();
30
+
31
+ router.use(requireAdmin as any);
32
+
33
+ // GET /api/terminal/sessions
34
+ router.get('/sessions', (_req, res) => {
35
+ const sessions = ptyManager.listSessions();
36
+ res.json({ sessions });
37
+ });
38
+
39
+ // POST /api/terminal/sessions
40
+ router.post('/sessions', (req, res) => {
41
+ const { projectId, projectName } = req.body;
42
+
43
+ if (!projectId || typeof projectId !== 'string') {
44
+ res.status(400).json({ error: 'projectId is required' });
45
+ return;
46
+ }
47
+ if (!projectName || typeof projectName !== 'string') {
48
+ res.status(400).json({ error: 'projectName is required' });
49
+ return;
50
+ }
51
+
52
+ const session = ptyManager.createSession(projectId.trim(), projectName.trim(), 80, 24);
53
+ log('TERMINAL', `Created session "${session.name}" for project ${projectId}`);
54
+ res.status(201).json({
55
+ session: {
56
+ id: session.id,
57
+ name: session.name,
58
+ projectId: session.projectId,
59
+ projectName: session.projectName,
60
+ state: session.state,
61
+ createdAt: session.createdAt.toISOString(),
62
+ },
63
+ });
64
+ });
65
+
66
+ // DELETE /api/terminal/sessions/:id
67
+ router.delete('/sessions/:id', (req, res) => {
68
+ const { id } = req.params;
69
+ const session = ptyManager.getSession(id);
70
+
71
+ if (!session) {
72
+ res.status(404).json({ error: 'Session not found' });
73
+ return;
74
+ }
75
+
76
+ ptyManager.destroySession(id);
77
+ log('TERMINAL', `Killed session ${id}`);
78
+ res.json({ ok: true });
79
+ });
80
+
81
+ return router;
82
+ };
@@ -16,7 +16,7 @@ import { fileURLToPath } from 'node:url';
16
16
  import { existsSync } from 'node:fs';
17
17
  import { WebSocketServer } from 'ws';
18
18
  import * as pty from 'node-pty';
19
- import { initServices, log } from './services/init.js';
19
+ import { initServices, log, githubAppService, workspaceService } from './services/init.js';
20
20
  import { AuthService } from './services/auth_service.js';
21
21
  import { EmailService } from './services/email_service.js';
22
22
  import { PasswordResetService } from './services/password_reset_service.js';
@@ -29,14 +29,16 @@ import { AuthMiddleware } from './middleware/auth_middleware.js';
29
29
  import { createAuthRoutes } from './routes/auth.js';
30
30
  import { createUserRoutes } from './routes/users.js';
31
31
  import { createProjectRoutes } from './routes/projects.js';
32
- import { getDb } from '@interview-system/shared/lib/db.js';
32
+ import { createTerminalRoutes } from './routes/terminal.js';
33
+ import { getDb } from '@assistkick/shared/lib/db.js';
33
34
  import graphRoutes from './routes/graph.js';
34
35
  import kanbanRoutes from './routes/kanban.js';
35
36
  import pipelineRoutes from './routes/pipeline.js';
36
37
  import coherenceRoutes from './routes/coherence.js';
38
+ import { createGitRoutes } from './routes/git.js';
37
39
 
38
40
  const __dirname = dirname(fileURLToPath(import.meta.url));
39
- const DEFAULT_PORT = 3000;
41
+ const DEFAULT_PORT = parseInt(process.env.PORT || '3000', 10);
40
42
 
41
43
  const parseArgs = (argv: string[]) => {
42
44
  const args = { port: DEFAULT_PORT, verbose: false };
@@ -68,8 +70,8 @@ app.use((req, res, next) => {
68
70
  const start = Date.now();
69
71
  res.on('finish', () => {
70
72
  const duration = Date.now() - start;
71
- // Skip noisy pipeline polls
72
- if (req.originalUrl.endsWith('/pipeline')) return;
73
+ // Skip noisy pipeline and orchestrator status polls
74
+ if (req.originalUrl.endsWith('/pipeline') || req.originalUrl.endsWith('/orchestrator-status')) return;
73
75
  log('HTTP', `${req.method} ${req.originalUrl} ${res.statusCode} ${duration}ms`);
74
76
  });
75
77
  next();
@@ -80,7 +82,7 @@ const jwtSecret = process.env.JWT_SECRET || 'dev-secret-change-in-production';
80
82
  const isProduction = process.env.NODE_ENV === 'production';
81
83
  const authService = new AuthService({ jwtSecret, isProduction });
82
84
  const resendApiKey = process.env.RESEND_API_KEY || '';
83
- const appBaseUrl = process.env.APP_BASE_URL || 'https://product-system.localhost';
85
+ const appBaseUrl = process.env.APP_BASE_URL || 'https://assistkick.localhost';
84
86
  const emailFromAddress = process.env.EMAIL_FROM || 'noreply@example.com';
85
87
  const emailService = new EmailService({ apiKey: resendApiKey, fromAddress: emailFromAddress });
86
88
  const passwordResetService = new PasswordResetService({ getDb, emailService, authService, appBaseUrl, log });
@@ -101,15 +103,29 @@ const projectService = new ProjectService({ getDb, log });
101
103
  const projectRoutes = createProjectRoutes({ projectService, log });
102
104
  app.use('/api/projects', authMiddleware.requireAuth, projectRoutes);
103
105
 
106
+ // Git repository routes (nested under /api/projects/:id/git)
107
+ const gitRoutes = createGitRoutes({ projectService, githubAppService, workspaceService, log });
108
+ app.use('/api/projects/:id/git', authMiddleware.requireAuth, gitRoutes);
109
+
104
110
  // Ensure default project exists and assign orphan nodes on startup
105
111
  projectService.ensureDefaultAndAssignOrphans().catch((err: any) => {
106
112
  log('STARTUP', `Failed to ensure default project: ${err.message}`);
107
113
  });
108
114
 
115
+ // PTY session manager — initialized early so terminal REST routes can reference it
116
+ // Resolve project root: from packages/backend/src → assistkick-product-system → repo root
117
+ const PROJECT_ROOT = join(__dirname, '..', '..', '..', '..');
118
+ const ptyManager = new PtySessionManager({ spawn: pty.spawn, log, projectRoot: PROJECT_ROOT });
119
+
120
+ // Terminal session management REST routes (admin-only, auth required)
121
+ const terminalRoutes = createTerminalRoutes({ ptyManager, log });
122
+ app.use('/api/terminal', authMiddleware.requireAuth, terminalRoutes);
123
+
109
124
  // Protected API routes — require authentication
110
125
  app.use('/api', authMiddleware.requireAuth, graphRoutes);
111
126
  app.use('/api/kanban', authMiddleware.requireAuth, kanbanRoutes);
112
127
  app.use('/api/kanban', authMiddleware.requireAuth, pipelineRoutes);
128
+ app.use('/api/pipeline', authMiddleware.requireAuth, pipelineRoutes);
113
129
  app.use('/api/coherence', authMiddleware.requireAuth, coherenceRoutes);
114
130
 
115
131
  // Redirect favicon.ico to SVG favicon served from static files
@@ -130,9 +146,6 @@ const server = createServer(app);
130
146
 
131
147
  // Set up WebSocket for terminal
132
148
  const wss = new WebSocketServer({ noServer: true });
133
- // Resolve project root: from packages/backend/src → product-system → repo root
134
- const PROJECT_ROOT = join(__dirname, '..', '..', '..', '..');
135
- const ptyManager = new PtySessionManager({ spawn: pty.spawn, log, projectRoot: PROJECT_ROOT });
136
149
  const terminalHandler = new TerminalWsHandler({ wss, authService, ptyManager, log });
137
150
 
138
151
  server.on('upgrade', (req, socket, head) => {
@@ -152,7 +165,7 @@ server.listen(args.port, () => {
152
165
  if (existsSync(FRONTEND_DIST)) {
153
166
  log('SERVER', `Serving frontend from ${FRONTEND_DIST}`);
154
167
  } else {
155
- log('SERVER', `No frontend dist found — run "pnpm --filter @interview-system/frontend build" first, or use dev mode`);
168
+ log('SERVER', `No frontend dist found — run "pnpm --filter @assistkick/frontend build" first, or use dev mode`);
156
169
  }
157
170
  });
158
171
 
@@ -4,10 +4,10 @@
4
4
 
5
5
  import { join } from 'node:path';
6
6
  import { randomUUID } from 'node:crypto';
7
- import { readGraph, removeEdge as removeEdgeDb, patchNode as patchNodeDb } from '@interview-system/shared/lib/graph.js';
8
- import { loadCoherence, saveCoherence, getDismissalKey, sortProposals } from '@interview-system/shared/lib/coherence.js';
9
- import { getDb } from '@interview-system/shared/lib/db.js';
10
- import { nodes } from '@interview-system/shared/db/schema.js';
7
+ import { readGraph, removeEdge as removeEdgeDb, patchNode as patchNodeDb } from '@assistkick/shared/lib/graph.js';
8
+ import { loadCoherence, saveCoherence, getDismissalKey, sortProposals } from '@assistkick/shared/lib/coherence.js';
9
+ import { getDb } from '@assistkick/shared/lib/db.js';
10
+ import { nodes } from '@assistkick/shared/db/schema.js';
11
11
  import { eq } from 'drizzle-orm';
12
12
  import { claudeService, paths, log } from './init.js';
13
13
 
@@ -0,0 +1,146 @@
1
+ /**
2
+ * GitHubAppService — generates installation access tokens for GitHub App authentication.
3
+ * Uses env vars GITHUB_APP_ID and GITHUB_APP_PRIVATE_KEY.
4
+ * Tokens are short-lived (1 hour) and scoped to a specific installation.
5
+ */
6
+
7
+ import { SignJWT, importPKCS8 } from 'jose';
8
+
9
+ interface GitHubAppServiceDeps {
10
+ log: (tag: string, ...args: any[]) => void;
11
+ }
12
+
13
+ interface Installation {
14
+ id: number;
15
+ account: { login: string; type: string };
16
+ app_id: number;
17
+ target_type: string;
18
+ }
19
+
20
+ interface InstallationRepo {
21
+ id: number;
22
+ full_name: string;
23
+ private: boolean;
24
+ default_branch: string;
25
+ clone_url: string;
26
+ }
27
+
28
+ export class GitHubAppService {
29
+ private readonly log: (tag: string, ...args: any[]) => void;
30
+
31
+ constructor({ log }: GitHubAppServiceDeps) {
32
+ this.log = log;
33
+ }
34
+
35
+ private getAppId = (): string => {
36
+ const appId = process.env.GITHUB_APP_ID;
37
+ if (!appId) throw new Error('GITHUB_APP_ID environment variable is not set');
38
+ return appId;
39
+ };
40
+
41
+ private getPrivateKey = (): string => {
42
+ const key = process.env.GITHUB_APP_PRIVATE_KEY;
43
+ if (!key) throw new Error('GITHUB_APP_PRIVATE_KEY environment variable is not set');
44
+ // Support both raw PEM and base64-encoded PEM (for env vars that can't contain newlines)
45
+ if (key.startsWith('-----BEGIN')) return key;
46
+ return Buffer.from(key, 'base64').toString('utf-8');
47
+ };
48
+
49
+ /** Check whether GitHub App credentials are configured. */
50
+ isConfigured = (): boolean => {
51
+ return !!(process.env.GITHUB_APP_ID && process.env.GITHUB_APP_PRIVATE_KEY);
52
+ };
53
+
54
+ /** Generate a JWT signed with the App's private key (valid for 10 minutes). */
55
+ private generateJwt = async (): Promise<string> => {
56
+ const appId = this.getAppId();
57
+ const privateKeyPem = this.getPrivateKey();
58
+ const privateKey = await importPKCS8(privateKeyPem, 'RS256');
59
+
60
+ const now = Math.floor(Date.now() / 1000);
61
+ const jwt = await new SignJWT({})
62
+ .setProtectedHeader({ alg: 'RS256' })
63
+ .setIssuer(appId)
64
+ .setIssuedAt(now - 60) // clock skew tolerance
65
+ .setExpirationTime(now + 600) // 10 minutes
66
+ .sign(privateKey);
67
+
68
+ return jwt;
69
+ };
70
+
71
+ /** Get a short-lived installation access token for git operations. */
72
+ getInstallationToken = async (installationId: string): Promise<string> => {
73
+ const jwt = await this.generateJwt();
74
+ this.log('GITHUB', `Requesting installation token for installation ${installationId}`);
75
+
76
+ const resp = await fetch(`https://api.github.com/app/installations/${installationId}/access_tokens`, {
77
+ method: 'POST',
78
+ headers: {
79
+ 'Authorization': `Bearer ${jwt}`,
80
+ 'Accept': 'application/vnd.github+json',
81
+ 'X-GitHub-Api-Version': '2022-11-28',
82
+ },
83
+ });
84
+
85
+ if (!resp.ok) {
86
+ const body = await resp.text();
87
+ throw new Error(`GitHub API error (${resp.status}): ${body}`);
88
+ }
89
+
90
+ const data = await resp.json();
91
+ this.log('GITHUB', `Installation token obtained, expires at ${data.expires_at}`);
92
+ return data.token;
93
+ };
94
+
95
+ /** List all installations of this GitHub App. */
96
+ listInstallations = async (): Promise<Installation[]> => {
97
+ const jwt = await this.generateJwt();
98
+
99
+ const resp = await fetch('https://api.github.com/app/installations', {
100
+ headers: {
101
+ 'Authorization': `Bearer ${jwt}`,
102
+ 'Accept': 'application/vnd.github+json',
103
+ 'X-GitHub-Api-Version': '2022-11-28',
104
+ },
105
+ });
106
+
107
+ if (!resp.ok) {
108
+ const body = await resp.text();
109
+ throw new Error(`GitHub API error (${resp.status}): ${body}`);
110
+ }
111
+
112
+ return resp.json();
113
+ };
114
+
115
+ /** List repositories accessible to a specific installation. */
116
+ listInstallationRepos = async (installationId: string): Promise<InstallationRepo[]> => {
117
+ const token = await this.getInstallationToken(installationId);
118
+
119
+ const resp = await fetch('https://api.github.com/installation/repositories?per_page=100', {
120
+ headers: {
121
+ 'Authorization': `token ${token}`,
122
+ 'Accept': 'application/vnd.github+json',
123
+ 'X-GitHub-Api-Version': '2022-11-28',
124
+ },
125
+ });
126
+
127
+ if (!resp.ok) {
128
+ const body = await resp.text();
129
+ throw new Error(`GitHub API error (${resp.status}): ${body}`);
130
+ }
131
+
132
+ const data = await resp.json();
133
+ return data.repositories;
134
+ };
135
+
136
+ /** Build an authenticated clone URL using an installation token. */
137
+ buildAuthenticatedCloneUrl = async (installationId: string, repoFullName: string): Promise<string> => {
138
+ const token = await this.getInstallationToken(installationId);
139
+ return `https://x-access-token:${token}@github.com/${repoFullName}.git`;
140
+ };
141
+
142
+ /** Configure git credential helper for a workspace to use installation tokens. */
143
+ buildGitCredentialUrl = (token: string, repoFullName: string): string => {
144
+ return `https://x-access-token:${token}@github.com/${repoFullName}.git`;
145
+ };
146
+ }
@@ -0,0 +1,147 @@
1
+ /**
2
+ * Service initialization — creates pipeline, claude service, and related instances.
3
+ * Deferred until VERBOSE flag is set at startup.
4
+ */
5
+
6
+ import { join, dirname } from 'node:path';
7
+ import { fileURLToPath } from 'node:url';
8
+ import { existsSync } from 'node:fs';
9
+ import { createClaudeService } from '@assistkick/shared/lib/claude-service.js';
10
+ import { Pipeline } from '@assistkick/shared/lib/pipeline.js';
11
+ import { PipelineStateStore } from '@assistkick/shared/lib/pipeline-state-store.js';
12
+ import { PromptBuilder } from '@assistkick/shared/lib/prompt_builder.js';
13
+ import { GitWorkflow } from '@assistkick/shared/lib/git_workflow.js';
14
+ import { getDb } from '@assistkick/shared/lib/db.js';
15
+ import { loadKanban, getKanbanEntry, saveKanbanEntry } from '@assistkick/shared/lib/kanban.js';
16
+ import { readGraph, getNode } from '@assistkick/shared/lib/graph.js';
17
+ import { WorkSummaryParser } from '@assistkick/shared/lib/work_summary_parser.js';
18
+ import { PipelineOrchestrator } from '@assistkick/shared/lib/pipeline_orchestrator.js';
19
+ import { GitHubAppService } from './github_app_service.js';
20
+ import { ProjectWorkspaceService } from './project_workspace_service.js';
21
+ import { ProjectService } from './project_service.js';
22
+
23
+ const __dirname = dirname(fileURLToPath(import.meta.url));
24
+ // Navigate from packages/backend/src/services/ up to assistkick-product-system/
25
+ const SKILL_ROOT = join(__dirname, '..', '..', '..', '..');
26
+ const SHARED_DIR = join(SKILL_ROOT, 'packages', 'shared');
27
+
28
+ const IS_DEV = import.meta.url.endsWith('.ts');
29
+ const TOOL_EXT = IS_DEV ? '.ts' : '.js';
30
+ const TOOL_RUNNER = IS_DEV ? 'tsx' : 'node';
31
+ const TOOLS_DIR = IS_DEV ? join(SHARED_DIR, 'tools') : join(__dirname, '..', '..', '..', 'shared', 'build', 'tools');
32
+ const DATA_DIR = SHARED_DIR;
33
+ const PROJECT_ROOT = join(SKILL_ROOT, '..');
34
+ const SKILLS_DIR = join(PROJECT_ROOT, '.claude', 'skills');
35
+ const WORKTREES_DIR = join(PROJECT_ROOT, '.worktrees');
36
+ const DEVELOPER_SKILL_PATH = join(SKILLS_DIR, 'product-developer', 'SKILL.md');
37
+ const REVIEWER_SKILL_PATH = join(SKILLS_DIR, 'product-code-reviewer', 'SKILL.md');
38
+ const DEBUGGER_SKILL_PATH = join(SKILLS_DIR, 'product-debugger', 'SKILL.md');
39
+
40
+ export const log = (tag: string, ...args: any[]) => {
41
+ const ts = new Date().toISOString().slice(11, 23);
42
+ console.log(`[${ts}] [${tag}]`, ...args);
43
+ };
44
+
45
+ export let claudeService: any;
46
+ export let pipeline: any;
47
+ export let pipelineStateStore: any;
48
+ export let orchestrator: PipelineOrchestrator;
49
+ export let githubAppService: GitHubAppService;
50
+ export let workspaceService: ProjectWorkspaceService;
51
+
52
+ export const paths = {
53
+ projectRoot: PROJECT_ROOT,
54
+ worktreesDir: WORKTREES_DIR,
55
+ skillsDir: SKILLS_DIR,
56
+ toolsDir: TOOLS_DIR,
57
+ dataDir: DATA_DIR,
58
+ developerSkillPath: DEVELOPER_SKILL_PATH,
59
+ reviewerSkillPath: REVIEWER_SKILL_PATH,
60
+ debuggerSkillPath: DEBUGGER_SKILL_PATH,
61
+ toolExt: TOOL_EXT,
62
+ toolRunner: TOOL_RUNNER,
63
+ };
64
+
65
+ export const initServices = (verbose: boolean) => {
66
+ claudeService = createClaudeService({ verbose, log });
67
+ pipelineStateStore = new PipelineStateStore({ getDb });
68
+ const promptBuilder = new PromptBuilder({ paths, log });
69
+ const gitWorkflow = new GitWorkflow({ claudeService, projectRoot: PROJECT_ROOT, worktreesDir: WORKTREES_DIR, log });
70
+ const workSummaryParser = new WorkSummaryParser();
71
+
72
+ // New services for project git repo integration
73
+ githubAppService = new GitHubAppService({ log });
74
+ workspaceService = new ProjectWorkspaceService({ claudeService, projectRoot: PROJECT_ROOT, log });
75
+ const projectService = new ProjectService({ getDb, log });
76
+
77
+ /**
78
+ * Resolve a project-scoped workspace for the pipeline.
79
+ * Returns a GitWorkflow pointing to the project workspace + an afterMerge hook
80
+ * that pushes to the remote. Returns null if no workspace is configured.
81
+ */
82
+ const resolveProjectWorkspace = async (projectId: string) => {
83
+ const project = await projectService.getById(projectId);
84
+ if (!project) return null;
85
+
86
+ // Check if project has a workspace
87
+ if (!workspaceService.hasWorkspace(projectId)) {
88
+ // No workspace — use default behavior
89
+ return null;
90
+ }
91
+
92
+ const wsPath = workspaceService.getWorkspacePath(projectId);
93
+ const wsWorktreesDir = workspaceService.getWorktreesDir(projectId);
94
+
95
+ const projectGitWorkflow = new GitWorkflow({
96
+ claudeService,
97
+ projectRoot: wsPath,
98
+ worktreesDir: wsWorktreesDir,
99
+ log,
100
+ });
101
+
102
+ // Build afterMerge hook for pushing to remote
103
+ let afterMerge = null;
104
+ if (project.repoUrl && project.githubInstallationId && project.githubRepoFullName) {
105
+ afterMerge = async () => {
106
+ // Get a fresh token for push
107
+ const token = await githubAppService.getInstallationToken(project.githubInstallationId!);
108
+ const authUrl = `https://x-access-token:${token}@github.com/${project.githubRepoFullName}.git`;
109
+ await projectGitWorkflow.setRemoteUrl(authUrl);
110
+ try {
111
+ const branch = project.baseBranch || 'main';
112
+ await projectGitWorkflow.pushToRemote(branch);
113
+ } finally {
114
+ // Reset to non-authenticated URL
115
+ await projectGitWorkflow.setRemoteUrl(`https://github.com/${project.githubRepoFullName}.git`).catch(() => {});
116
+ }
117
+ };
118
+ }
119
+
120
+ return { gitWorkflow: projectGitWorkflow, afterMerge };
121
+ };
122
+
123
+ pipeline = new Pipeline({
124
+ promptBuilder,
125
+ gitWorkflow,
126
+ claudeService,
127
+ kanban: { getKanbanEntry, saveKanbanEntry },
128
+ paths,
129
+ log,
130
+ stateStore: pipelineStateStore,
131
+ workSummaryParser,
132
+ getNode,
133
+ resolveProjectWorkspace,
134
+ });
135
+
136
+ orchestrator = new PipelineOrchestrator({
137
+ pipeline,
138
+ loadKanban,
139
+ readGraph,
140
+ log,
141
+ });
142
+
143
+ // Mark any active pipeline states as interrupted (server restart recovery)
144
+ pipelineStateStore.markInterrupted().catch((err: any) => {
145
+ log('STARTUP', `Failed to mark interrupted pipelines: ${err.message}`);
146
+ });
147
+ };
@@ -6,7 +6,7 @@
6
6
  import { randomBytes, randomUUID } from 'node:crypto';
7
7
  import { hash, compare } from 'bcryptjs';
8
8
  import { eq } from 'drizzle-orm';
9
- import { users, invitations } from '@interview-system/shared/db/schema.js';
9
+ import { users, invitations } from '@assistkick/shared/db/schema.js';
10
10
  import type { EmailService } from './email_service.js';
11
11
  import type { AuthService } from './auth_service.js';
12
12
 
@@ -6,7 +6,7 @@
6
6
  import { randomBytes, randomUUID } from 'node:crypto';
7
7
  import { hash, compare } from 'bcryptjs';
8
8
  import { eq, and } from 'drizzle-orm';
9
- import { users, passwordResetTokens } from '@interview-system/shared/db/schema.js';
9
+ import { users, passwordResetTokens } from '@assistkick/shared/db/schema.js';
10
10
  import type { EmailService } from './email_service.js';
11
11
  import type { AuthService } from './auth_service.js';
12
12
 
@@ -4,7 +4,7 @@
4
4
  */
5
5
 
6
6
  import { eq, isNull } from 'drizzle-orm';
7
- import { projects, nodes, edges, kanban, sessions } from '@interview-system/shared/db/schema.js';
7
+ import { projects, nodes, edges, kanban, sessions } from '@assistkick/shared/db/schema.js';
8
8
  import { randomBytes } from 'node:crypto';
9
9
 
10
10
  interface ProjectServiceDeps {
@@ -17,6 +17,10 @@ export interface ProjectRecord {
17
17
  name: string;
18
18
  isDefault: number;
19
19
  archivedAt: string | null;
20
+ repoUrl: string | null;
21
+ githubInstallationId: string | null;
22
+ githubRepoFullName: string | null;
23
+ baseBranch: string | null;
20
24
  createdAt: string;
21
25
  updatedAt: string;
22
26
  }
@@ -64,6 +68,10 @@ export class ProjectService {
64
68
  name,
65
69
  isDefault: 0,
66
70
  archivedAt: null,
71
+ repoUrl: null,
72
+ githubInstallationId: null,
73
+ githubRepoFullName: null,
74
+ baseBranch: null,
67
75
  createdAt: now,
68
76
  updatedAt: now,
69
77
  };
@@ -139,6 +147,69 @@ export class ProjectService {
139
147
  return { ...existing, archivedAt: null, updatedAt: now };
140
148
  };
141
149
 
150
+ /** Connect a git repository to a project (stores repo metadata). */
151
+ connectRepo = async (id: string, opts: {
152
+ repoUrl: string;
153
+ githubInstallationId?: string;
154
+ githubRepoFullName?: string;
155
+ baseBranch?: string;
156
+ }): Promise<ProjectRecord> => {
157
+ const db = this.getDb();
158
+ const existing = await this.getById(id);
159
+ if (!existing) throw new Error('Project not found');
160
+
161
+ const now = new Date().toISOString();
162
+ await db
163
+ .update(projects)
164
+ .set({
165
+ repoUrl: opts.repoUrl,
166
+ githubInstallationId: opts.githubInstallationId || null,
167
+ githubRepoFullName: opts.githubRepoFullName || null,
168
+ baseBranch: opts.baseBranch || null,
169
+ updatedAt: now,
170
+ })
171
+ .where(eq(projects.id, id));
172
+
173
+ this.log('PROJECTS', `Connected repo to project ${id}: ${opts.repoUrl}`);
174
+ return {
175
+ ...existing,
176
+ repoUrl: opts.repoUrl,
177
+ githubInstallationId: opts.githubInstallationId || null,
178
+ githubRepoFullName: opts.githubRepoFullName || null,
179
+ baseBranch: opts.baseBranch || null,
180
+ updatedAt: now,
181
+ };
182
+ };
183
+
184
+ /** Disconnect a git repository from a project. */
185
+ disconnectRepo = async (id: string): Promise<ProjectRecord> => {
186
+ const db = this.getDb();
187
+ const existing = await this.getById(id);
188
+ if (!existing) throw new Error('Project not found');
189
+
190
+ const now = new Date().toISOString();
191
+ await db
192
+ .update(projects)
193
+ .set({
194
+ repoUrl: null,
195
+ githubInstallationId: null,
196
+ githubRepoFullName: null,
197
+ baseBranch: null,
198
+ updatedAt: now,
199
+ })
200
+ .where(eq(projects.id, id));
201
+
202
+ this.log('PROJECTS', `Disconnected repo from project ${id}`);
203
+ return {
204
+ ...existing,
205
+ repoUrl: null,
206
+ githubInstallationId: null,
207
+ githubRepoFullName: null,
208
+ baseBranch: null,
209
+ updatedAt: now,
210
+ };
211
+ };
212
+
142
213
  ensureDefaultAndAssignOrphans = async (): Promise<void> => {
143
214
  const db = this.getDb();
144
215