@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.
- package/dist/src/scaffolder.d.ts +6 -1
- package/dist/src/scaffolder.js +20 -9
- package/dist/src/scaffolder.js.map +1 -1
- package/package.json +3 -2
- package/templates/{product-system → assistkick-product-system}/CLAUDE.md +4 -4
- package/templates/{product-system → assistkick-product-system}/package.json +5 -5
- package/templates/{product-system → assistkick-product-system}/packages/backend/package.json +2 -2
- package/templates/{product-system → assistkick-product-system}/packages/backend/src/routes/auth.ts +1 -1
- package/templates/{product-system → assistkick-product-system}/packages/backend/src/routes/coherence.ts +1 -1
- package/templates/assistkick-product-system/packages/backend/src/routes/git.ts +231 -0
- package/templates/{product-system → assistkick-product-system}/packages/backend/src/routes/graph.ts +3 -3
- package/templates/{product-system → assistkick-product-system}/packages/backend/src/routes/kanban.ts +6 -6
- package/templates/assistkick-product-system/packages/backend/src/routes/pipeline.ts +88 -0
- package/templates/assistkick-product-system/packages/backend/src/routes/terminal.ts +82 -0
- package/templates/{product-system → assistkick-product-system}/packages/backend/src/server.ts +23 -10
- package/templates/{product-system → assistkick-product-system}/packages/backend/src/services/coherence-review.ts +4 -4
- package/templates/assistkick-product-system/packages/backend/src/services/github_app_service.ts +146 -0
- package/templates/assistkick-product-system/packages/backend/src/services/init.ts +147 -0
- package/templates/{product-system → assistkick-product-system}/packages/backend/src/services/invitation_service.ts +1 -1
- package/templates/{product-system → assistkick-product-system}/packages/backend/src/services/password_reset_service.ts +1 -1
- package/templates/{product-system → assistkick-product-system}/packages/backend/src/services/project_service.ts +72 -1
- package/templates/assistkick-product-system/packages/backend/src/services/project_workspace_service.test.ts +87 -0
- package/templates/assistkick-product-system/packages/backend/src/services/project_workspace_service.ts +194 -0
- package/templates/assistkick-product-system/packages/backend/src/services/pty_session_manager.test.ts +159 -0
- package/templates/{product-system → assistkick-product-system}/packages/backend/src/services/pty_session_manager.ts +114 -39
- package/templates/{product-system → assistkick-product-system}/packages/backend/src/services/terminal_ws_handler.ts +28 -14
- package/templates/{product-system → assistkick-product-system}/packages/backend/src/services/user_management_service.ts +1 -1
- package/templates/{product-system → assistkick-product-system}/packages/frontend/package.json +1 -1
- package/templates/{product-system → assistkick-product-system}/packages/frontend/src/App.tsx +1 -1
- package/templates/{product-system → assistkick-product-system}/packages/frontend/src/api/client.ts +151 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/GitRepoModal.tsx +352 -0
- package/templates/{product-system → assistkick-product-system}/packages/frontend/src/components/KanbanView.tsx +208 -95
- package/templates/{product-system → assistkick-product-system}/packages/frontend/src/components/ProjectSelector.tsx +17 -1
- package/templates/assistkick-product-system/packages/frontend/src/components/TerminalView.tsx +333 -0
- package/templates/{product-system → assistkick-product-system}/packages/frontend/src/components/Toolbar.tsx +15 -13
- package/templates/{product-system → assistkick-product-system}/packages/frontend/src/constants/graph.ts +1 -0
- package/templates/{product-system → assistkick-product-system}/packages/frontend/src/hooks/useProjects.ts +4 -0
- package/templates/{product-system → assistkick-product-system}/packages/frontend/src/routes/dashboard.tsx +22 -4
- package/templates/{product-system → assistkick-product-system}/packages/frontend/src/styles/index.css +486 -38
- package/templates/assistkick-product-system/packages/frontend/vite.config.ts +31 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/0001_vengeful_wallop.sql +1 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/0002_greedy_excalibur.sql +4 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0001_snapshot.json +826 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0002_snapshot.json +854 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/meta/_journal.json +27 -0
- package/templates/{product-system → assistkick-product-system}/packages/shared/db/schema.ts +5 -0
- package/templates/{product-system → assistkick-product-system}/packages/shared/lib/claude-service.ts +54 -1
- package/templates/{product-system → assistkick-product-system}/packages/shared/lib/db.ts +1 -1
- package/templates/{product-system → assistkick-product-system}/packages/shared/lib/git_workflow.ts +25 -0
- package/templates/{product-system → assistkick-product-system}/packages/shared/lib/pipeline-state-store.ts +4 -0
- package/templates/{product-system → assistkick-product-system}/packages/shared/lib/pipeline.ts +329 -89
- package/templates/assistkick-product-system/packages/shared/lib/pipeline_orchestrator.ts +186 -0
- package/templates/{product-system → assistkick-product-system}/packages/shared/lib/prompt_builder.ts +2 -2
- package/templates/{product-system → assistkick-product-system}/packages/shared/package.json +1 -1
- package/templates/assistkick-product-system/packages/shared/tools/db_explorer.ts +275 -0
- package/templates/{product-system → assistkick-product-system}/packages/shared/tools/get_kanban.ts +2 -1
- package/templates/{product-system → assistkick-product-system}/packages/shared/tools/move_card.ts +3 -2
- package/templates/{product-system → assistkick-product-system}/packages/shared/tools/update_node.ts +2 -2
- package/templates/{product-system → assistkick-product-system}/tests/db_sqlite_fallback.test.ts +1 -1
- package/templates/{product-system → assistkick-product-system}/tests/kanban.test.ts +1 -1
- package/templates/{product-system → assistkick-product-system}/tests/pipeline_stats_all_cards.test.ts +1 -1
- package/templates/{product-system → assistkick-product-system}/tests/web_terminal.test.ts +189 -150
- package/templates/skills/{product-bootstrap → assistkick-bootstrap}/SKILL.md +36 -28
- package/templates/skills/{product-code-reviewer → assistkick-code-reviewer}/SKILL.md +26 -18
- package/templates/skills/assistkick-db-explorer/SKILL.md +86 -0
- package/templates/skills/{product-debugger → assistkick-debugger}/SKILL.md +35 -27
- package/templates/skills/{product-developer → assistkick-developer}/SKILL.md +40 -32
- package/templates/skills/{product-interview → assistkick-interview}/SKILL.md +37 -29
- package/templates/product-system/packages/backend/src/routes/pipeline.ts +0 -41
- package/templates/product-system/packages/backend/src/services/init.ts +0 -80
- package/templates/product-system/packages/backend/src/services/pty_session_manager.test.ts +0 -88
- package/templates/product-system/packages/frontend/src/components/TerminalView.tsx +0 -200
- package/templates/product-system/packages/frontend/vite.config.ts +0 -20
- package/templates/product-system/packages/shared/db/migrations/meta/_journal.json +0 -13
- /package/templates/{product-system → assistkick-product-system}/.env.example +0 -0
- /package/templates/{product-system → assistkick-product-system}/packages/backend/src/middleware/auth_middleware.test.ts +0 -0
- /package/templates/{product-system → assistkick-product-system}/packages/backend/src/middleware/auth_middleware.ts +0 -0
- /package/templates/{product-system → assistkick-product-system}/packages/backend/src/routes/projects.ts +0 -0
- /package/templates/{product-system → assistkick-product-system}/packages/backend/src/routes/users.ts +0 -0
- /package/templates/{product-system → assistkick-product-system}/packages/backend/src/services/auth_service.test.ts +0 -0
- /package/templates/{product-system → assistkick-product-system}/packages/backend/src/services/auth_service.ts +0 -0
- /package/templates/{product-system → assistkick-product-system}/packages/backend/src/services/email_service.ts +0 -0
- /package/templates/{product-system → assistkick-product-system}/packages/backend/src/services/invitation_service.test.ts +0 -0
- /package/templates/{product-system → assistkick-product-system}/packages/backend/src/services/password_reset_service.test.ts +0 -0
- /package/templates/{product-system → assistkick-product-system}/packages/backend/src/services/project_service.test.ts +0 -0
- /package/templates/{product-system → assistkick-product-system}/packages/backend/src/services/user_management_service.test.ts +0 -0
- /package/templates/{product-system → assistkick-product-system}/packages/backend/tsconfig.json +0 -0
- /package/templates/{product-system → assistkick-product-system}/packages/frontend/index.html +0 -0
- /package/templates/{product-system → assistkick-product-system}/packages/frontend/package-lock.json +0 -0
- /package/templates/{product-system → assistkick-product-system}/packages/frontend/public/favicon.svg +0 -0
- /package/templates/{product-system → assistkick-product-system}/packages/frontend/src/api/client_projects.test.ts +0 -0
- /package/templates/{product-system → assistkick-product-system}/packages/frontend/src/api/client_refresh.test.ts +0 -0
- /package/templates/{product-system → assistkick-product-system}/packages/frontend/src/components/CoherenceView.tsx +0 -0
- /package/templates/{product-system → assistkick-product-system}/packages/frontend/src/components/GraphLegend.tsx +0 -0
- /package/templates/{product-system → assistkick-product-system}/packages/frontend/src/components/GraphSettings.tsx +0 -0
- /package/templates/{product-system → assistkick-product-system}/packages/frontend/src/components/GraphView.tsx +0 -0
- /package/templates/{product-system → assistkick-product-system}/packages/frontend/src/components/InviteUserDialog.tsx +0 -0
- /package/templates/{product-system → assistkick-product-system}/packages/frontend/src/components/LoginPage.tsx +0 -0
- /package/templates/{product-system → assistkick-product-system}/packages/frontend/src/components/QaIssueSheet.tsx +0 -0
- /package/templates/{product-system → assistkick-product-system}/packages/frontend/src/components/SidePanel.tsx +0 -0
- /package/templates/{product-system → assistkick-product-system}/packages/frontend/src/components/UsersView.tsx +0 -0
- /package/templates/{product-system → assistkick-product-system}/packages/frontend/src/hooks/useAuth.tsx +0 -0
- /package/templates/{product-system → assistkick-product-system}/packages/frontend/src/hooks/useGraph.ts +0 -0
- /package/templates/{product-system → assistkick-product-system}/packages/frontend/src/hooks/useKanban.ts +0 -0
- /package/templates/{product-system → assistkick-product-system}/packages/frontend/src/hooks/useTheme.ts +0 -0
- /package/templates/{product-system → assistkick-product-system}/packages/frontend/src/hooks/useToast.tsx +0 -0
- /package/templates/{product-system → assistkick-product-system}/packages/frontend/src/hooks/use_projects_logic.test.ts +0 -0
- /package/templates/{product-system → assistkick-product-system}/packages/frontend/src/main.tsx +0 -0
- /package/templates/{product-system → assistkick-product-system}/packages/frontend/src/pages/accept_invitation_page.tsx +0 -0
- /package/templates/{product-system → assistkick-product-system}/packages/frontend/src/pages/forgot_password_page.tsx +0 -0
- /package/templates/{product-system → assistkick-product-system}/packages/frontend/src/pages/register_page.tsx +0 -0
- /package/templates/{product-system → assistkick-product-system}/packages/frontend/src/pages/reset_password_page.tsx +0 -0
- /package/templates/{product-system → assistkick-product-system}/packages/frontend/src/routes/ProtectedRoute.tsx +0 -0
- /package/templates/{product-system → assistkick-product-system}/packages/frontend/src/routes/accept_invitation.tsx +0 -0
- /package/templates/{product-system → assistkick-product-system}/packages/frontend/src/routes/forgot_password.tsx +0 -0
- /package/templates/{product-system → assistkick-product-system}/packages/frontend/src/routes/login.tsx +0 -0
- /package/templates/{product-system → assistkick-product-system}/packages/frontend/src/routes/register.tsx +0 -0
- /package/templates/{product-system → assistkick-product-system}/packages/frontend/src/routes/reset_password.tsx +0 -0
- /package/templates/{product-system → assistkick-product-system}/packages/frontend/src/utils/auth_validation.test.ts +0 -0
- /package/templates/{product-system → assistkick-product-system}/packages/frontend/src/utils/auth_validation.ts +0 -0
- /package/templates/{product-system → assistkick-product-system}/packages/frontend/src/utils/login_validation.test.ts +0 -0
- /package/templates/{product-system → assistkick-product-system}/packages/frontend/src/utils/login_validation.ts +0 -0
- /package/templates/{product-system → assistkick-product-system}/packages/frontend/src/utils/logout.test.ts +0 -0
- /package/templates/{product-system → assistkick-product-system}/packages/frontend/src/utils/node_sizing.test.ts +0 -0
- /package/templates/{product-system → assistkick-product-system}/packages/frontend/src/utils/node_sizing.ts +0 -0
- /package/templates/{product-system → assistkick-product-system}/packages/frontend/src/utils/task_status.test.ts +0 -0
- /package/templates/{product-system → assistkick-product-system}/packages/frontend/src/utils/task_status.ts +0 -0
- /package/templates/{product-system → assistkick-product-system}/packages/frontend/tsconfig.json +0 -0
- /package/templates/{product-system → assistkick-product-system}/packages/shared/.env.example +0 -0
- /package/templates/{product-system → assistkick-product-system}/packages/shared/README.md +0 -0
- /package/templates/{product-system → assistkick-product-system}/packages/shared/db/migrate.ts +0 -0
- /package/templates/{product-system → assistkick-product-system}/packages/shared/db/migrations/0000_dashing_gorgon.sql +0 -0
- /package/templates/{product-system → assistkick-product-system}/packages/shared/db/migrations/meta/0000_snapshot.json +0 -0
- /package/templates/{product-system → assistkick-product-system}/packages/shared/drizzle.config.js +0 -0
- /package/templates/{product-system → assistkick-product-system}/packages/shared/lib/coherence.ts +0 -0
- /package/templates/{product-system → assistkick-product-system}/packages/shared/lib/completeness.ts +0 -0
- /package/templates/{product-system → assistkick-product-system}/packages/shared/lib/constants.ts +0 -0
- /package/templates/{product-system → assistkick-product-system}/packages/shared/lib/graph.ts +0 -0
- /package/templates/{product-system → assistkick-product-system}/packages/shared/lib/kanban.ts +0 -0
- /package/templates/{product-system → assistkick-product-system}/packages/shared/lib/markdown.ts +0 -0
- /package/templates/{product-system → assistkick-product-system}/packages/shared/lib/relevance_search.ts +0 -0
- /package/templates/{product-system → assistkick-product-system}/packages/shared/lib/session.ts +0 -0
- /package/templates/{product-system → assistkick-product-system}/packages/shared/lib/validator.ts +0 -0
- /package/templates/{product-system → assistkick-product-system}/packages/shared/lib/work_summary_parser.ts +0 -0
- /package/templates/{product-system → assistkick-product-system}/packages/shared/scripts/assign-project.ts +0 -0
- /package/templates/{product-system → assistkick-product-system}/packages/shared/tools/add_edge.ts +0 -0
- /package/templates/{product-system → assistkick-product-system}/packages/shared/tools/add_node.ts +0 -0
- /package/templates/{product-system → assistkick-product-system}/packages/shared/tools/end_session.ts +0 -0
- /package/templates/{product-system → assistkick-product-system}/packages/shared/tools/get_gaps.ts +0 -0
- /package/templates/{product-system → assistkick-product-system}/packages/shared/tools/get_node.ts +0 -0
- /package/templates/{product-system → assistkick-product-system}/packages/shared/tools/get_status.ts +0 -0
- /package/templates/{product-system → assistkick-product-system}/packages/shared/tools/migrate_to_turso.ts +0 -0
- /package/templates/{product-system → assistkick-product-system}/packages/shared/tools/rebuild_index.ts +0 -0
- /package/templates/{product-system → assistkick-product-system}/packages/shared/tools/remove_edge.ts +0 -0
- /package/templates/{product-system → assistkick-product-system}/packages/shared/tools/remove_node.ts +0 -0
- /package/templates/{product-system → assistkick-product-system}/packages/shared/tools/resolve_question.ts +0 -0
- /package/templates/{product-system → assistkick-product-system}/packages/shared/tools/search_nodes.ts +0 -0
- /package/templates/{product-system → assistkick-product-system}/packages/shared/tools/start_session.ts +0 -0
- /package/templates/{product-system → assistkick-product-system}/packages/shared/tsconfig.json +0 -0
- /package/templates/{product-system → assistkick-product-system}/pnpm-workspace.yaml +0 -0
- /package/templates/{product-system → assistkick-product-system}/smoke_test.ts +0 -0
- /package/templates/{product-system → assistkick-product-system}/tests/coherence_review.test.ts +0 -0
- /package/templates/{product-system → assistkick-product-system}/tests/edge_type_color_coding.test.ts +0 -0
- /package/templates/{product-system → assistkick-product-system}/tests/emit-tool-use-events.test.ts +0 -0
- /package/templates/{product-system → assistkick-product-system}/tests/feature_kind.test.ts +0 -0
- /package/templates/{product-system → assistkick-product-system}/tests/gap_indicators.test.ts +0 -0
- /package/templates/{product-system → assistkick-product-system}/tests/graceful_init.test.ts +0 -0
- /package/templates/{product-system → assistkick-product-system}/tests/graph_legend.test.ts +0 -0
- /package/templates/{product-system → assistkick-product-system}/tests/graph_settings_sheet.test.ts +0 -0
- /package/templates/{product-system → assistkick-product-system}/tests/hide_defined_filter.test.ts +0 -0
- /package/templates/{product-system → assistkick-product-system}/tests/neighborhood_focus.test.ts +0 -0
- /package/templates/{product-system → assistkick-product-system}/tests/node_search.test.ts +0 -0
- /package/templates/{product-system → assistkick-product-system}/tests/node_sizing.test.ts +0 -0
- /package/templates/{product-system → assistkick-product-system}/tests/node_type_toggle_filters.test.ts +0 -0
- /package/templates/{product-system → assistkick-product-system}/tests/node_type_visual_encoding.test.ts +0 -0
- /package/templates/{product-system → assistkick-product-system}/tests/pipeline-state-store.test.ts +0 -0
- /package/templates/{product-system → assistkick-product-system}/tests/pipeline-unit.test.ts +0 -0
- /package/templates/{product-system → assistkick-product-system}/tests/pipeline.test.ts +0 -0
- /package/templates/{product-system → assistkick-product-system}/tests/play_all.test.ts +0 -0
- /package/templates/{product-system → assistkick-product-system}/tests/qa_issue_sheet.test.ts +0 -0
- /package/templates/{product-system → assistkick-product-system}/tests/relevance_search.test.ts +0 -0
- /package/templates/{product-system → assistkick-product-system}/tests/search_reorder.test.ts +0 -0
- /package/templates/{product-system → assistkick-product-system}/tests/serve_ui.test.ts +0 -0
- /package/templates/{product-system → assistkick-product-system}/tests/serve_ui_drizzle.test.ts +0 -0
- /package/templates/{product-system → assistkick-product-system}/tests/session_context_recall.test.ts +0 -0
- /package/templates/{product-system → assistkick-product-system}/tests/side_panel.test.ts +0 -0
- /package/templates/{product-system → assistkick-product-system}/tests/spec_completeness_label.test.ts +0 -0
- /package/templates/{product-system → assistkick-product-system}/tests/url_routing_test.ts +0 -0
- /package/templates/{product-system → assistkick-product-system}/tests/user_login.test.ts +0 -0
- /package/templates/{product-system → assistkick-product-system}/tests/user_registration.test.ts +0 -0
- /package/templates/{product-system → assistkick-product-system}/tests/work_summary.test.ts +0 -0
- /package/templates/{product-system → assistkick-product-system}/tests/zoom_pan.test.ts +0 -0
- /package/templates/{product-system → assistkick-product-system}/tsconfig.json +0 -0
- /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
|
+
};
|
package/templates/{product-system → assistkick-product-system}/packages/backend/src/server.ts
RENAMED
|
@@ -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 {
|
|
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://
|
|
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 @
|
|
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 '@
|
|
8
|
-
import { loadCoherence, saveCoherence, getDismissalKey, sortProposals } from '@
|
|
9
|
-
import { getDb } from '@
|
|
10
|
-
import { nodes } from '@
|
|
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
|
|
package/templates/assistkick-product-system/packages/backend/src/services/github_app_service.ts
ADDED
|
@@ -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 '@
|
|
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 '@
|
|
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 '@
|
|
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
|
|