@assistkick/create 1.0.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 (178) hide show
  1. package/dist/bin/create.d.ts +2 -0
  2. package/dist/bin/create.js +25 -0
  3. package/dist/bin/create.js.map +1 -0
  4. package/dist/src/scaffolder.d.ts +22 -0
  5. package/dist/src/scaffolder.js +120 -0
  6. package/dist/src/scaffolder.js.map +1 -0
  7. package/package.json +24 -0
  8. package/templates/product-system/.env.example +8 -0
  9. package/templates/product-system/CLAUDE.md +45 -0
  10. package/templates/product-system/package.json +32 -0
  11. package/templates/product-system/packages/backend/package.json +37 -0
  12. package/templates/product-system/packages/backend/src/middleware/auth_middleware.test.ts +86 -0
  13. package/templates/product-system/packages/backend/src/middleware/auth_middleware.ts +35 -0
  14. package/templates/product-system/packages/backend/src/routes/auth.ts +463 -0
  15. package/templates/product-system/packages/backend/src/routes/coherence.ts +187 -0
  16. package/templates/product-system/packages/backend/src/routes/graph.ts +67 -0
  17. package/templates/product-system/packages/backend/src/routes/kanban.ts +201 -0
  18. package/templates/product-system/packages/backend/src/routes/pipeline.ts +41 -0
  19. package/templates/product-system/packages/backend/src/routes/projects.ts +122 -0
  20. package/templates/product-system/packages/backend/src/routes/users.ts +97 -0
  21. package/templates/product-system/packages/backend/src/server.ts +159 -0
  22. package/templates/product-system/packages/backend/src/services/auth_service.test.ts +115 -0
  23. package/templates/product-system/packages/backend/src/services/auth_service.ts +82 -0
  24. package/templates/product-system/packages/backend/src/services/coherence-review.ts +339 -0
  25. package/templates/product-system/packages/backend/src/services/email_service.ts +75 -0
  26. package/templates/product-system/packages/backend/src/services/init.ts +80 -0
  27. package/templates/product-system/packages/backend/src/services/invitation_service.test.ts +235 -0
  28. package/templates/product-system/packages/backend/src/services/invitation_service.ts +193 -0
  29. package/templates/product-system/packages/backend/src/services/password_reset_service.test.ts +151 -0
  30. package/templates/product-system/packages/backend/src/services/password_reset_service.ts +135 -0
  31. package/templates/product-system/packages/backend/src/services/project_service.test.ts +215 -0
  32. package/templates/product-system/packages/backend/src/services/project_service.ts +171 -0
  33. package/templates/product-system/packages/backend/src/services/pty_session_manager.test.ts +88 -0
  34. package/templates/product-system/packages/backend/src/services/pty_session_manager.ts +279 -0
  35. package/templates/product-system/packages/backend/src/services/terminal_ws_handler.ts +133 -0
  36. package/templates/product-system/packages/backend/src/services/user_management_service.test.ts +158 -0
  37. package/templates/product-system/packages/backend/src/services/user_management_service.ts +128 -0
  38. package/templates/product-system/packages/backend/tsconfig.json +22 -0
  39. package/templates/product-system/packages/frontend/index.html +13 -0
  40. package/templates/product-system/packages/frontend/package-lock.json +2666 -0
  41. package/templates/product-system/packages/frontend/package.json +30 -0
  42. package/templates/product-system/packages/frontend/public/favicon.svg +16 -0
  43. package/templates/product-system/packages/frontend/src/App.tsx +29 -0
  44. package/templates/product-system/packages/frontend/src/api/client.ts +386 -0
  45. package/templates/product-system/packages/frontend/src/api/client_projects.test.ts +104 -0
  46. package/templates/product-system/packages/frontend/src/api/client_refresh.test.ts +145 -0
  47. package/templates/product-system/packages/frontend/src/components/CoherenceView.tsx +414 -0
  48. package/templates/product-system/packages/frontend/src/components/GraphLegend.tsx +124 -0
  49. package/templates/product-system/packages/frontend/src/components/GraphSettings.tsx +112 -0
  50. package/templates/product-system/packages/frontend/src/components/GraphView.tsx +370 -0
  51. package/templates/product-system/packages/frontend/src/components/InviteUserDialog.tsx +85 -0
  52. package/templates/product-system/packages/frontend/src/components/KanbanView.tsx +470 -0
  53. package/templates/product-system/packages/frontend/src/components/LoginPage.tsx +116 -0
  54. package/templates/product-system/packages/frontend/src/components/ProjectSelector.tsx +187 -0
  55. package/templates/product-system/packages/frontend/src/components/QaIssueSheet.tsx +192 -0
  56. package/templates/product-system/packages/frontend/src/components/SidePanel.tsx +231 -0
  57. package/templates/product-system/packages/frontend/src/components/TerminalView.tsx +200 -0
  58. package/templates/product-system/packages/frontend/src/components/Toolbar.tsx +84 -0
  59. package/templates/product-system/packages/frontend/src/components/UsersView.tsx +249 -0
  60. package/templates/product-system/packages/frontend/src/constants/graph.ts +191 -0
  61. package/templates/product-system/packages/frontend/src/hooks/useAuth.tsx +54 -0
  62. package/templates/product-system/packages/frontend/src/hooks/useGraph.ts +27 -0
  63. package/templates/product-system/packages/frontend/src/hooks/useKanban.ts +21 -0
  64. package/templates/product-system/packages/frontend/src/hooks/useProjects.ts +86 -0
  65. package/templates/product-system/packages/frontend/src/hooks/useTheme.ts +26 -0
  66. package/templates/product-system/packages/frontend/src/hooks/useToast.tsx +62 -0
  67. package/templates/product-system/packages/frontend/src/hooks/use_projects_logic.test.ts +61 -0
  68. package/templates/product-system/packages/frontend/src/main.tsx +12 -0
  69. package/templates/product-system/packages/frontend/src/pages/accept_invitation_page.tsx +167 -0
  70. package/templates/product-system/packages/frontend/src/pages/forgot_password_page.tsx +100 -0
  71. package/templates/product-system/packages/frontend/src/pages/register_page.tsx +137 -0
  72. package/templates/product-system/packages/frontend/src/pages/reset_password_page.tsx +146 -0
  73. package/templates/product-system/packages/frontend/src/routes/ProtectedRoute.tsx +12 -0
  74. package/templates/product-system/packages/frontend/src/routes/accept_invitation.tsx +14 -0
  75. package/templates/product-system/packages/frontend/src/routes/dashboard.tsx +221 -0
  76. package/templates/product-system/packages/frontend/src/routes/forgot_password.tsx +13 -0
  77. package/templates/product-system/packages/frontend/src/routes/login.tsx +14 -0
  78. package/templates/product-system/packages/frontend/src/routes/register.tsx +14 -0
  79. package/templates/product-system/packages/frontend/src/routes/reset_password.tsx +13 -0
  80. package/templates/product-system/packages/frontend/src/styles/index.css +3358 -0
  81. package/templates/product-system/packages/frontend/src/utils/auth_validation.test.ts +51 -0
  82. package/templates/product-system/packages/frontend/src/utils/auth_validation.ts +19 -0
  83. package/templates/product-system/packages/frontend/src/utils/login_validation.test.ts +61 -0
  84. package/templates/product-system/packages/frontend/src/utils/login_validation.ts +24 -0
  85. package/templates/product-system/packages/frontend/src/utils/logout.test.ts +63 -0
  86. package/templates/product-system/packages/frontend/src/utils/node_sizing.test.ts +62 -0
  87. package/templates/product-system/packages/frontend/src/utils/node_sizing.ts +24 -0
  88. package/templates/product-system/packages/frontend/src/utils/task_status.test.ts +53 -0
  89. package/templates/product-system/packages/frontend/src/utils/task_status.ts +14 -0
  90. package/templates/product-system/packages/frontend/tsconfig.json +21 -0
  91. package/templates/product-system/packages/frontend/vite.config.ts +20 -0
  92. package/templates/product-system/packages/shared/.env.example +3 -0
  93. package/templates/product-system/packages/shared/README.md +1 -0
  94. package/templates/product-system/packages/shared/db/migrate.ts +32 -0
  95. package/templates/product-system/packages/shared/db/migrations/0000_dashing_gorgon.sql +128 -0
  96. package/templates/product-system/packages/shared/db/migrations/meta/0000_snapshot.json +819 -0
  97. package/templates/product-system/packages/shared/db/migrations/meta/_journal.json +13 -0
  98. package/templates/product-system/packages/shared/db/schema.ts +137 -0
  99. package/templates/product-system/packages/shared/drizzle.config.js +14 -0
  100. package/templates/product-system/packages/shared/lib/claude-service.ts +215 -0
  101. package/templates/product-system/packages/shared/lib/coherence.ts +278 -0
  102. package/templates/product-system/packages/shared/lib/completeness.ts +30 -0
  103. package/templates/product-system/packages/shared/lib/constants.ts +327 -0
  104. package/templates/product-system/packages/shared/lib/db.ts +81 -0
  105. package/templates/product-system/packages/shared/lib/git_workflow.ts +110 -0
  106. package/templates/product-system/packages/shared/lib/graph.ts +186 -0
  107. package/templates/product-system/packages/shared/lib/kanban.ts +161 -0
  108. package/templates/product-system/packages/shared/lib/markdown.ts +205 -0
  109. package/templates/product-system/packages/shared/lib/pipeline-state-store.ts +124 -0
  110. package/templates/product-system/packages/shared/lib/pipeline.ts +489 -0
  111. package/templates/product-system/packages/shared/lib/prompt_builder.ts +170 -0
  112. package/templates/product-system/packages/shared/lib/relevance_search.ts +159 -0
  113. package/templates/product-system/packages/shared/lib/session.ts +152 -0
  114. package/templates/product-system/packages/shared/lib/validator.ts +117 -0
  115. package/templates/product-system/packages/shared/lib/work_summary_parser.ts +130 -0
  116. package/templates/product-system/packages/shared/package.json +30 -0
  117. package/templates/product-system/packages/shared/scripts/assign-project.ts +52 -0
  118. package/templates/product-system/packages/shared/tools/add_edge.ts +61 -0
  119. package/templates/product-system/packages/shared/tools/add_node.ts +101 -0
  120. package/templates/product-system/packages/shared/tools/end_session.ts +87 -0
  121. package/templates/product-system/packages/shared/tools/get_gaps.ts +87 -0
  122. package/templates/product-system/packages/shared/tools/get_kanban.ts +125 -0
  123. package/templates/product-system/packages/shared/tools/get_node.ts +78 -0
  124. package/templates/product-system/packages/shared/tools/get_status.ts +98 -0
  125. package/templates/product-system/packages/shared/tools/migrate_to_turso.ts +385 -0
  126. package/templates/product-system/packages/shared/tools/move_card.ts +143 -0
  127. package/templates/product-system/packages/shared/tools/rebuild_index.ts +77 -0
  128. package/templates/product-system/packages/shared/tools/remove_edge.ts +59 -0
  129. package/templates/product-system/packages/shared/tools/remove_node.ts +96 -0
  130. package/templates/product-system/packages/shared/tools/resolve_question.ts +75 -0
  131. package/templates/product-system/packages/shared/tools/search_nodes.ts +106 -0
  132. package/templates/product-system/packages/shared/tools/start_session.ts +144 -0
  133. package/templates/product-system/packages/shared/tools/update_node.ts +133 -0
  134. package/templates/product-system/packages/shared/tsconfig.json +24 -0
  135. package/templates/product-system/pnpm-workspace.yaml +2 -0
  136. package/templates/product-system/smoke_test.ts +219 -0
  137. package/templates/product-system/tests/coherence_review.test.ts +562 -0
  138. package/templates/product-system/tests/db_sqlite_fallback.test.ts +75 -0
  139. package/templates/product-system/tests/edge_type_color_coding.test.ts +147 -0
  140. package/templates/product-system/tests/emit-tool-use-events.test.ts +85 -0
  141. package/templates/product-system/tests/feature_kind.test.ts +139 -0
  142. package/templates/product-system/tests/gap_indicators.test.ts +199 -0
  143. package/templates/product-system/tests/graceful_init.test.ts +142 -0
  144. package/templates/product-system/tests/graph_legend.test.ts +314 -0
  145. package/templates/product-system/tests/graph_settings_sheet.test.ts +804 -0
  146. package/templates/product-system/tests/hide_defined_filter.test.ts +205 -0
  147. package/templates/product-system/tests/kanban.test.ts +529 -0
  148. package/templates/product-system/tests/neighborhood_focus.test.ts +132 -0
  149. package/templates/product-system/tests/node_search.test.ts +340 -0
  150. package/templates/product-system/tests/node_sizing.test.ts +170 -0
  151. package/templates/product-system/tests/node_type_toggle_filters.test.ts +285 -0
  152. package/templates/product-system/tests/node_type_visual_encoding.test.ts +103 -0
  153. package/templates/product-system/tests/pipeline-state-store.test.ts +268 -0
  154. package/templates/product-system/tests/pipeline-unit.test.ts +593 -0
  155. package/templates/product-system/tests/pipeline.test.ts +195 -0
  156. package/templates/product-system/tests/pipeline_stats_all_cards.test.ts +193 -0
  157. package/templates/product-system/tests/play_all.test.ts +296 -0
  158. package/templates/product-system/tests/qa_issue_sheet.test.ts +464 -0
  159. package/templates/product-system/tests/relevance_search.test.ts +186 -0
  160. package/templates/product-system/tests/search_reorder.test.ts +88 -0
  161. package/templates/product-system/tests/serve_ui.test.ts +281 -0
  162. package/templates/product-system/tests/serve_ui_drizzle.test.ts +114 -0
  163. package/templates/product-system/tests/session_context_recall.test.ts +135 -0
  164. package/templates/product-system/tests/side_panel.test.ts +345 -0
  165. package/templates/product-system/tests/spec_completeness_label.test.ts +69 -0
  166. package/templates/product-system/tests/url_routing_test.ts +122 -0
  167. package/templates/product-system/tests/user_login.test.ts +150 -0
  168. package/templates/product-system/tests/user_registration.test.ts +205 -0
  169. package/templates/product-system/tests/web_terminal.test.ts +572 -0
  170. package/templates/product-system/tests/work_summary.test.ts +211 -0
  171. package/templates/product-system/tests/zoom_pan.test.ts +43 -0
  172. package/templates/product-system/tsconfig.json +24 -0
  173. package/templates/skills/product-bootstrap/SKILL.md +312 -0
  174. package/templates/skills/product-code-reviewer/SKILL.md +147 -0
  175. package/templates/skills/product-debugger/SKILL.md +206 -0
  176. package/templates/skills/product-debugger/references/agent-browser.md +1156 -0
  177. package/templates/skills/product-developer/SKILL.md +182 -0
  178. package/templates/skills/product-interview/SKILL.md +220 -0
@@ -0,0 +1,51 @@
1
+ import { describe, it } from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { validateEmail, validatePassword, MIN_PASSWORD_LENGTH } from './auth_validation.ts';
4
+
5
+ describe('validateEmail', () => {
6
+ it('returns error for empty string', () => {
7
+ assert.equal(validateEmail(''), 'Email is required');
8
+ });
9
+
10
+ it('returns error for whitespace-only string', () => {
11
+ assert.equal(validateEmail(' '), 'Email is required');
12
+ });
13
+
14
+ it('returns error for invalid email format', () => {
15
+ assert.equal(validateEmail('notanemail'), 'Invalid email format');
16
+ });
17
+
18
+ it('returns error for email without domain', () => {
19
+ assert.equal(validateEmail('user@'), 'Invalid email format');
20
+ });
21
+
22
+ it('returns error for email without TLD', () => {
23
+ assert.equal(validateEmail('user@domain'), 'Invalid email format');
24
+ });
25
+
26
+ it('returns undefined for valid email', () => {
27
+ assert.equal(validateEmail('admin@example.com'), undefined);
28
+ });
29
+
30
+ it('returns undefined for valid email with subdomain', () => {
31
+ assert.equal(validateEmail('user@mail.example.com'), undefined);
32
+ });
33
+ });
34
+
35
+ describe('validatePassword', () => {
36
+ it('returns error for empty string', () => {
37
+ assert.equal(validatePassword(''), 'Password is required');
38
+ });
39
+
40
+ it('returns error for short password', () => {
41
+ assert.equal(validatePassword('1234567'), `Password must be at least ${MIN_PASSWORD_LENGTH} characters`);
42
+ });
43
+
44
+ it('returns undefined for exactly 8 characters', () => {
45
+ assert.equal(validatePassword('12345678'), undefined);
46
+ });
47
+
48
+ it('returns undefined for long password', () => {
49
+ assert.equal(validatePassword('a-very-long-password-indeed'), undefined);
50
+ });
51
+ });
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Validation utilities for auth forms (registration, login).
3
+ * Pure functions — no React dependencies.
4
+ */
5
+
6
+ const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
7
+ export const MIN_PASSWORD_LENGTH = 8;
8
+
9
+ export const validateEmail = (email: string): string | undefined => {
10
+ if (!email.trim()) return 'Email is required';
11
+ if (!EMAIL_REGEX.test(email)) return 'Invalid email format';
12
+ return undefined;
13
+ };
14
+
15
+ export const validatePassword = (password: string): string | undefined => {
16
+ if (!password) return 'Password is required';
17
+ if (password.length < MIN_PASSWORD_LENGTH) return `Password must be at least ${MIN_PASSWORD_LENGTH} characters`;
18
+ return undefined;
19
+ };
@@ -0,0 +1,61 @@
1
+ import { describe, it } from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { validateLoginForm, parseLoginResponse } from './login_validation.ts';
4
+
5
+ describe('validateLoginForm', () => {
6
+ it('returns error when email is empty', () => {
7
+ assert.equal(validateLoginForm('', 'password123'), 'Email and password are required');
8
+ });
9
+
10
+ it('returns error when email is whitespace only', () => {
11
+ assert.equal(validateLoginForm(' ', 'password123'), 'Email and password are required');
12
+ });
13
+
14
+ it('returns error when password is empty', () => {
15
+ assert.equal(validateLoginForm('user@example.com', ''), 'Email and password are required');
16
+ });
17
+
18
+ it('returns error when both are empty', () => {
19
+ assert.equal(validateLoginForm('', ''), 'Email and password are required');
20
+ });
21
+
22
+ it('returns empty string for valid inputs', () => {
23
+ assert.equal(validateLoginForm('user@example.com', 'password123'), '');
24
+ });
25
+
26
+ it('trims email before checking', () => {
27
+ assert.equal(validateLoginForm(' user@example.com ', 'pass'), '');
28
+ });
29
+ });
30
+
31
+ describe('parseLoginResponse', () => {
32
+ it('returns user data for successful response', async () => {
33
+ const mockResp = {
34
+ ok: true,
35
+ json: async () => ({ user: { id: '1', email: 'a@b.com', role: 'admin' } }),
36
+ } as Response;
37
+
38
+ const result = await parseLoginResponse(mockResp);
39
+ assert.deepEqual(result, { user: { id: '1', email: 'a@b.com', role: 'admin' } });
40
+ });
41
+
42
+ it('returns error from API response body', async () => {
43
+ const mockResp = {
44
+ ok: false,
45
+ json: async () => ({ error: 'Invalid email or password' }),
46
+ } as Response;
47
+
48
+ const result = await parseLoginResponse(mockResp);
49
+ assert.deepEqual(result, { error: 'Invalid email or password' });
50
+ });
51
+
52
+ it('returns generic error when response body is not JSON', async () => {
53
+ const mockResp = {
54
+ ok: false,
55
+ json: async () => { throw new Error('not json'); },
56
+ } as unknown as Response;
57
+
58
+ const result = await parseLoginResponse(mockResp);
59
+ assert.deepEqual(result, { error: 'Login failed' });
60
+ });
61
+ });
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Login form validation logic.
3
+ * Returns an error message string if invalid, or empty string if valid.
4
+ */
5
+
6
+ export const validateLoginForm = (email: string, password: string): string => {
7
+ if (!email.trim() || !password) {
8
+ return 'Email and password are required';
9
+ }
10
+ return '';
11
+ };
12
+
13
+ /**
14
+ * Parses the login API response and returns either user data or an error string.
15
+ */
16
+ export const parseLoginResponse = async (
17
+ resp: Response
18
+ ): Promise<{ user: { id: string; email: string; role: string } } | { error: string }> => {
19
+ if (!resp.ok) {
20
+ const data = await resp.json().catch(() => null);
21
+ return { error: data?.error || 'Login failed' };
22
+ }
23
+ return resp.json();
24
+ };
@@ -0,0 +1,63 @@
1
+ import { describe, it, mock, beforeEach, afterEach } from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { ApiClient } from '../api/client.ts';
4
+
5
+ describe('ApiClient.logout', () => {
6
+ let originalFetch: typeof globalThis.fetch;
7
+
8
+ beforeEach(() => {
9
+ originalFetch = globalThis.fetch;
10
+ });
11
+
12
+ afterEach(() => {
13
+ globalThis.fetch = originalFetch;
14
+ });
15
+
16
+ it('calls POST /api/auth/logout with same-origin credentials', async () => {
17
+ let capturedUrl = '';
18
+ let capturedInit: RequestInit | undefined;
19
+
20
+ globalThis.fetch = mock.fn(async (url: any, init?: any) => {
21
+ capturedUrl = url;
22
+ capturedInit = init;
23
+ return { ok: true, json: async () => ({ message: 'Logged out' }) } as Response;
24
+ }) as any;
25
+
26
+ const client = new ApiClient();
27
+ const result = await client.logout();
28
+
29
+ assert.equal(capturedUrl, '/api/auth/logout');
30
+ assert.equal(capturedInit?.method, 'POST');
31
+ assert.equal(capturedInit?.credentials, 'same-origin');
32
+ assert.deepEqual(result, { message: 'Logged out' });
33
+ });
34
+
35
+ it('throws when server returns non-ok response', async () => {
36
+ globalThis.fetch = mock.fn(async () => {
37
+ return { ok: false, status: 500 } as Response;
38
+ }) as any;
39
+
40
+ const client = new ApiClient();
41
+ await assert.rejects(
42
+ () => client.logout(),
43
+ (err: Error) => {
44
+ assert.match(err.message, /Logout failed/);
45
+ return true;
46
+ },
47
+ );
48
+ });
49
+
50
+ it('uses baseUrl when configured', async () => {
51
+ let capturedUrl = '';
52
+
53
+ globalThis.fetch = mock.fn(async (url: any) => {
54
+ capturedUrl = url;
55
+ return { ok: true, json: async () => ({ message: 'Logged out' }) } as Response;
56
+ }) as any;
57
+
58
+ const client = new ApiClient('http://localhost:3000');
59
+ await client.logout();
60
+
61
+ assert.equal(capturedUrl, 'http://localhost:3000/api/auth/logout');
62
+ });
63
+ });
@@ -0,0 +1,62 @@
1
+ import { describe, it } from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { clampedRadius, absoluteRadius, nodeRadius, SCALE_FACTOR } from './node_sizing.ts';
4
+
5
+ describe('clampedRadius', () => {
6
+ it('returns base of 8 for zero connections', () => {
7
+ assert.equal(clampedRadius(0), 8);
8
+ });
9
+
10
+ it('scales linearly for low edge counts', () => {
11
+ assert.equal(clampedRadius(1), 8 + 1.5);
12
+ assert.equal(clampedRadius(4), 8 + 6);
13
+ });
14
+
15
+ it('caps the scale at 20 for high edge counts', () => {
16
+ // edgeCount * 1.5 > 20 when edgeCount > 13.33
17
+ assert.equal(clampedRadius(14), 8 + 20);
18
+ assert.equal(clampedRadius(100), 8 + 20);
19
+ });
20
+
21
+ it('maximum radius is 28', () => {
22
+ assert.equal(clampedRadius(999), 28);
23
+ });
24
+ });
25
+
26
+ describe('absoluteRadius', () => {
27
+ it('uses SCALE_FACTOR of 2', () => {
28
+ assert.equal(SCALE_FACTOR, 2);
29
+ });
30
+
31
+ it('returns minimum of 2 for zero connections', () => {
32
+ assert.equal(absoluteRadius(0), 2);
33
+ });
34
+
35
+ it('returns edgeCount * 2 for normal counts', () => {
36
+ assert.equal(absoluteRadius(1), 2);
37
+ assert.equal(absoluteRadius(5), 10);
38
+ assert.equal(absoluteRadius(45), 90);
39
+ });
40
+
41
+ it('has no upper cap', () => {
42
+ assert.equal(absoluteRadius(100), 200);
43
+ });
44
+ });
45
+
46
+ describe('nodeRadius', () => {
47
+ it('uses clamped mode when absoluteSizing is false', () => {
48
+ assert.equal(nodeRadius(10, false), clampedRadius(10));
49
+ });
50
+
51
+ it('uses absolute mode when absoluteSizing is true', () => {
52
+ assert.equal(nodeRadius(10, true), absoluteRadius(10));
53
+ });
54
+
55
+ it('default (clamped) caps at 28px', () => {
56
+ assert.equal(nodeRadius(50, false), 28);
57
+ });
58
+
59
+ it('absolute mode allows large radii', () => {
60
+ assert.equal(nodeRadius(50, true), 100);
61
+ });
62
+ });
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Node radius calculation utilities for the force graph.
3
+ *
4
+ * Clamped mode (default): radius = 8 + min(edgeCount * 1.5, 20), capped at 28.
5
+ * Absolute mode: radius = edgeCount * 2 (no cap), minimum 2px.
6
+ */
7
+
8
+ export const SCALE_FACTOR = 2;
9
+ const CLAMPED_BASE = 8;
10
+ const CLAMPED_MAX_SCALE = 20;
11
+ const ABSOLUTE_MIN_RADIUS = 2;
12
+
13
+ export const clampedRadius = (edgeCount: number): number => {
14
+ const scale = Math.min(edgeCount * 1.5, CLAMPED_MAX_SCALE);
15
+ return CLAMPED_BASE + scale;
16
+ };
17
+
18
+ export const absoluteRadius = (edgeCount: number): number => {
19
+ return Math.max(edgeCount * SCALE_FACTOR, ABSOLUTE_MIN_RADIUS);
20
+ };
21
+
22
+ export const nodeRadius = (edgeCount: number, absoluteSizing: boolean): number => {
23
+ return absoluteSizing ? absoluteRadius(edgeCount) : clampedRadius(edgeCount);
24
+ };
@@ -0,0 +1,53 @@
1
+ import { describe, it } from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { getTaskIcon, getTaskCssClass, shouldShowTaskList, TASK_ICONS } from './task_status.ts';
4
+
5
+ describe('getTaskIcon', () => {
6
+ it('returns checkmark for completed', () => {
7
+ assert.equal(getTaskIcon('completed'), '\u2713');
8
+ });
9
+
10
+ it('returns filled circle for in_progress', () => {
11
+ assert.equal(getTaskIcon('in_progress'), '\u25CF');
12
+ });
13
+
14
+ it('returns empty circle for pending', () => {
15
+ assert.equal(getTaskIcon('pending'), '\u25CB');
16
+ });
17
+
18
+ it('falls back to pending icon for unknown status', () => {
19
+ assert.equal(getTaskIcon('unknown'), TASK_ICONS.pending);
20
+ });
21
+ });
22
+
23
+ describe('getTaskCssClass', () => {
24
+ it('returns correct class for completed', () => {
25
+ assert.equal(getTaskCssClass('completed'), 'kanban-task-item task-completed');
26
+ });
27
+
28
+ it('returns correct class for in_progress', () => {
29
+ assert.equal(getTaskCssClass('in_progress'), 'kanban-task-item task-in_progress');
30
+ });
31
+
32
+ it('returns correct class for pending', () => {
33
+ assert.equal(getTaskCssClass('pending'), 'kanban-task-item task-pending');
34
+ });
35
+ });
36
+
37
+ describe('shouldShowTaskList', () => {
38
+ it('returns true when total > 0', () => {
39
+ assert.equal(shouldShowTaskList({ total: 4 }), true);
40
+ });
41
+
42
+ it('returns false when total is 0', () => {
43
+ assert.equal(shouldShowTaskList({ total: 0 }), false);
44
+ });
45
+
46
+ it('returns false for null', () => {
47
+ assert.equal(shouldShowTaskList(null), false);
48
+ });
49
+
50
+ it('returns false for undefined', () => {
51
+ assert.equal(shouldShowTaskList(undefined), false);
52
+ });
53
+ });
@@ -0,0 +1,14 @@
1
+ export const TASK_ICONS: Record<string, string> = {
2
+ completed: '\u2713',
3
+ in_progress: '\u25CF',
4
+ pending: '\u25CB',
5
+ };
6
+
7
+ export const getTaskIcon = (status: string): string =>
8
+ TASK_ICONS[status] ?? TASK_ICONS.pending;
9
+
10
+ export const getTaskCssClass = (status: string): string =>
11
+ `kanban-task-item task-${status}`;
12
+
13
+ export const shouldShowTaskList = (tasks: { total: number } | undefined | null): boolean =>
14
+ !!tasks && tasks.total > 0;
@@ -0,0 +1,21 @@
1
+ {
2
+ "compilerOptions": {
3
+ "strict": false,
4
+ "target": "ESNext",
5
+ "module": "ESNext",
6
+ "moduleResolution": "bundler",
7
+ "jsx": "react-jsx",
8
+ "esModuleInterop": true,
9
+ "skipLibCheck": true,
10
+ "forceConsistentCasingInFileNames": true,
11
+ "isolatedModules": true,
12
+ "noEmit": true
13
+ },
14
+ "include": [
15
+ "src/**/*.ts",
16
+ "src/**/*.tsx"
17
+ ],
18
+ "exclude": [
19
+ "node_modules"
20
+ ]
21
+ }
@@ -0,0 +1,20 @@
1
+ import { defineConfig } from 'vite';
2
+ import react from '@vitejs/plugin-react';
3
+
4
+ export default defineConfig({
5
+ plugins: [react()],
6
+ build: {
7
+ outDir: 'dist',
8
+ emptyOutDir: true,
9
+ },
10
+ server: {
11
+ port: 5173,
12
+ strictPort: true,
13
+ proxy: {
14
+ '/api': {
15
+ target: 'http://localhost:3000',
16
+ ws: true,
17
+ },
18
+ },
19
+ },
20
+ });
@@ -0,0 +1,3 @@
1
+ # Turso database credentials
2
+ TURSO_DATABASE_URL=libsql://your-database.turso.io
3
+ TURSO_AUTH_TOKEN=your-auth-token
@@ -0,0 +1 @@
1
+ pnpm db:assign-project -- proj_abc12345
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Programmatic migration runner for Turso/libSQL.
3
+ * Uses drizzle-orm/libsql/migrator instead of drizzle-kit CLI,
4
+ * which has a known bug where it doesn't create __drizzle_migrations.
5
+ */
6
+ import { config } from 'dotenv';
7
+ config({ path: '../../.env' });
8
+
9
+ import { migrate } from 'drizzle-orm/libsql/migrator';
10
+ import { drizzle } from 'drizzle-orm/libsql';
11
+ import { createClient } from '@libsql/client';
12
+
13
+ const client = createClient({
14
+ url: process.env.TURSO_DATABASE_URL!,
15
+ authToken: process.env.TURSO_AUTH_TOKEN!,
16
+ });
17
+
18
+ const db = drizzle(client);
19
+
20
+ async function main() {
21
+ console.log('Running migrations...');
22
+ await migrate(db, { migrationsFolder: './db/migrations' });
23
+ console.log('Migrations applied successfully.');
24
+ client.close();
25
+ process.exit(0);
26
+ }
27
+
28
+ main().catch((err) => {
29
+ console.error('Migration failed:', err);
30
+ client.close();
31
+ process.exit(1);
32
+ });
@@ -0,0 +1,128 @@
1
+ CREATE TABLE `coherence_reviews` (
2
+ `id` text PRIMARY KEY NOT NULL,
3
+ `type` text NOT NULL,
4
+ `from_id` text,
5
+ `to_id` text,
6
+ `relation` text,
7
+ `proposed_node_type` text,
8
+ `proposed_node_name` text,
9
+ `target_node_id` text,
10
+ `proposed_description` text,
11
+ `confidence` text,
12
+ `batch_id` text,
13
+ `reasoning` text NOT NULL,
14
+ `status` text NOT NULL,
15
+ `created_at` text NOT NULL,
16
+ `resolved_at` text,
17
+ `project_id` text
18
+ );
19
+ --> statement-breakpoint
20
+ CREATE TABLE `edges` (
21
+ `from_id` text NOT NULL,
22
+ `relation` text NOT NULL,
23
+ `to_id` text NOT NULL,
24
+ `project_id` text,
25
+ PRIMARY KEY(`from_id`, `relation`, `to_id`)
26
+ );
27
+ --> statement-breakpoint
28
+ CREATE TABLE `invitations` (
29
+ `id` text PRIMARY KEY NOT NULL,
30
+ `email` text NOT NULL,
31
+ `token_hash` text NOT NULL,
32
+ `invited_by` text NOT NULL,
33
+ `expires_at` text NOT NULL,
34
+ `accepted_at` text,
35
+ `created_at` text NOT NULL,
36
+ FOREIGN KEY (`invited_by`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE no action
37
+ );
38
+ --> statement-breakpoint
39
+ CREATE TABLE `kanban` (
40
+ `node_id` text PRIMARY KEY NOT NULL,
41
+ `column_name` text NOT NULL,
42
+ `position` integer NOT NULL,
43
+ `project_id` text,
44
+ FOREIGN KEY (`node_id`) REFERENCES `nodes`(`id`) ON UPDATE no action ON DELETE no action
45
+ );
46
+ --> statement-breakpoint
47
+ CREATE TABLE `nodes` (
48
+ `id` text PRIMARY KEY NOT NULL,
49
+ `type` text NOT NULL,
50
+ `name` text NOT NULL,
51
+ `status` text NOT NULL,
52
+ `priority` text NOT NULL,
53
+ `completeness` real DEFAULT 0 NOT NULL,
54
+ `open_questions_count` integer DEFAULT 0 NOT NULL,
55
+ `kind` text,
56
+ `created_at` text NOT NULL,
57
+ `updated_at` text NOT NULL,
58
+ `body` text,
59
+ `project_id` text
60
+ );
61
+ --> statement-breakpoint
62
+ CREATE TABLE `password_reset_tokens` (
63
+ `id` text PRIMARY KEY NOT NULL,
64
+ `user_id` text NOT NULL,
65
+ `token_hash` text NOT NULL,
66
+ `expires_at` text NOT NULL,
67
+ `created_at` text NOT NULL,
68
+ `used_at` text,
69
+ FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE no action
70
+ );
71
+ --> statement-breakpoint
72
+ CREATE TABLE `pipeline_state` (
73
+ `feature_id` text PRIMARY KEY NOT NULL,
74
+ `status` text NOT NULL,
75
+ `cycle` integer DEFAULT 1 NOT NULL,
76
+ `tasks_json` text,
77
+ `tool_calls_json` text,
78
+ `work_summaries_json` text,
79
+ `error` text,
80
+ `updated_at` text NOT NULL,
81
+ `project_id` text
82
+ );
83
+ --> statement-breakpoint
84
+ CREATE TABLE `projects` (
85
+ `id` text PRIMARY KEY NOT NULL,
86
+ `name` text NOT NULL,
87
+ `is_default` integer DEFAULT 0 NOT NULL,
88
+ `archived_at` text,
89
+ `created_at` text NOT NULL,
90
+ `updated_at` text NOT NULL
91
+ );
92
+ --> statement-breakpoint
93
+ CREATE TABLE `refresh_tokens` (
94
+ `id` text PRIMARY KEY NOT NULL,
95
+ `user_id` text NOT NULL,
96
+ `token_hash` text NOT NULL,
97
+ `expires_at` text NOT NULL,
98
+ `created_at` text NOT NULL,
99
+ FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE no action
100
+ );
101
+ --> statement-breakpoint
102
+ CREATE TABLE `review_meta` (
103
+ `key` text PRIMARY KEY NOT NULL,
104
+ `value` text NOT NULL,
105
+ `project_id` text
106
+ );
107
+ --> statement-breakpoint
108
+ CREATE TABLE `sessions` (
109
+ `session_number` integer PRIMARY KEY NOT NULL,
110
+ `started_at` text NOT NULL,
111
+ `ended_at` text,
112
+ `summary` text,
113
+ `nodes_touched` text,
114
+ `questions_resolved` integer DEFAULT 0 NOT NULL,
115
+ `body` text,
116
+ `project_id` text
117
+ );
118
+ --> statement-breakpoint
119
+ CREATE TABLE `users` (
120
+ `id` text PRIMARY KEY NOT NULL,
121
+ `email` text NOT NULL,
122
+ `password_hash` text NOT NULL,
123
+ `role` text DEFAULT 'user' NOT NULL,
124
+ `created_at` text NOT NULL,
125
+ `updated_at` text NOT NULL
126
+ );
127
+ --> statement-breakpoint
128
+ CREATE UNIQUE INDEX `users_email_unique` ON `users` (`email`);