jettypod 4.4.120 → 4.4.121

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 (208) hide show
  1. package/.env +2 -1
  2. package/Cargo.lock +6450 -0
  3. package/Cargo.toml +35 -0
  4. package/README.md +5 -1
  5. package/TAURI-MIGRATION-PLAN.md +840 -0
  6. package/apps/dashboard/app/connect-claude/page.tsx +5 -6
  7. package/apps/dashboard/app/decision/[id]/page.tsx +54 -49
  8. package/apps/dashboard/app/demo/gates/page.tsx +3 -5
  9. package/apps/dashboard/app/design-system/page.tsx +1 -1
  10. package/apps/dashboard/app/globals.css +74 -2
  11. package/apps/dashboard/app/install-claude/page.tsx +3 -5
  12. package/apps/dashboard/app/login/page.tsx +17 -20
  13. package/apps/dashboard/app/page.tsx +101 -48
  14. package/apps/dashboard/app/settings/page.tsx +60 -12
  15. package/apps/dashboard/app/signup/page.tsx +14 -17
  16. package/apps/dashboard/app/subscribe/page.tsx +0 -2
  17. package/apps/dashboard/app/tests/page.tsx +37 -4
  18. package/apps/dashboard/app/welcome/page.tsx +12 -15
  19. package/apps/dashboard/app/work/[id]/page.tsx +90 -75
  20. package/apps/dashboard/app/work/[id]/proof/page.tsx +1489 -0
  21. package/apps/dashboard/components/AppShell.tsx +70 -61
  22. package/apps/dashboard/components/CardMenu.tsx +0 -1
  23. package/apps/dashboard/components/ClaudePanel.tsx +541 -283
  24. package/apps/dashboard/components/ClaudePanelInput.tsx +23 -4
  25. package/apps/dashboard/components/ConnectClaudeScreen.tsx +1 -5
  26. package/apps/dashboard/components/CopyableId.tsx +1 -2
  27. package/apps/dashboard/components/DetailReviewActions.tsx +11 -20
  28. package/apps/dashboard/components/DragContext.tsx +132 -62
  29. package/apps/dashboard/components/DraggableCard.tsx +3 -5
  30. package/apps/dashboard/components/DropZone.tsx +5 -6
  31. package/apps/dashboard/components/EditableDetailDescription.tsx +6 -12
  32. package/apps/dashboard/components/EditableDetailTitle.tsx +6 -13
  33. package/apps/dashboard/components/EditableTitle.tsx +0 -1
  34. package/apps/dashboard/components/ElapsedTimer.tsx +15 -3
  35. package/apps/dashboard/components/EpicGroup.tsx +100 -70
  36. package/apps/dashboard/components/GateCard.tsx +0 -1
  37. package/apps/dashboard/components/GateChoiceCard.tsx +1 -2
  38. package/apps/dashboard/components/InstallClaudeScreen.tsx +1 -5
  39. package/apps/dashboard/components/JettyLoader.tsx +0 -1
  40. package/apps/dashboard/components/KanbanBoard.tsx +319 -173
  41. package/apps/dashboard/components/KanbanCard.tsx +341 -107
  42. package/apps/dashboard/components/LazyCard.tsx +62 -0
  43. package/apps/dashboard/components/LazyMarkdown.tsx +0 -1
  44. package/apps/dashboard/components/MainNav.tsx +24 -25
  45. package/apps/dashboard/components/MessageBlock.tsx +93 -16
  46. package/apps/dashboard/components/ModeStartCard.tsx +0 -1
  47. package/apps/dashboard/components/OnboardingWelcome.tsx +0 -1
  48. package/apps/dashboard/components/PlaceholderCard.tsx +0 -1
  49. package/apps/dashboard/components/ProjectSwitcher.tsx +20 -20
  50. package/apps/dashboard/components/PrototypeTimeline.tsx +47 -26
  51. package/apps/dashboard/components/RealTimeKanbanWrapper.tsx +308 -223
  52. package/apps/dashboard/components/RealTimeTestsWrapper.tsx +303 -160
  53. package/apps/dashboard/components/ReviewFooter.tsx +12 -14
  54. package/apps/dashboard/components/SessionList.tsx +0 -1
  55. package/apps/dashboard/components/SubscribeContent.tsx +40 -11
  56. package/apps/dashboard/components/TestTree.tsx +1 -2
  57. package/apps/dashboard/components/TipCard.tsx +2 -4
  58. package/apps/dashboard/components/Toast.tsx +0 -1
  59. package/apps/dashboard/components/TypeIcon.tsx +7 -8
  60. package/apps/dashboard/components/ViewModeToolbar.tsx +104 -0
  61. package/apps/dashboard/components/WaveCompletionAnimation.tsx +5 -17
  62. package/apps/dashboard/components/WelcomeScreen.tsx +2 -6
  63. package/apps/dashboard/components/WorkItemHeader.tsx +0 -1
  64. package/apps/dashboard/components/WorkItemTree.tsx +2 -4
  65. package/apps/dashboard/components/settings/AccountSection.tsx +27 -13
  66. package/apps/dashboard/components/settings/AiContextSection.tsx +89 -0
  67. package/apps/dashboard/components/settings/ContextDocumentsSection.tsx +317 -0
  68. package/apps/dashboard/components/settings/EnvVarsSection.tsx +20 -73
  69. package/apps/dashboard/components/settings/GeneralSection.tsx +137 -26
  70. package/apps/dashboard/components/settings/ProjectStackSection.tsx +948 -0
  71. package/apps/dashboard/components/settings/SettingsLayout.tsx +0 -1
  72. package/apps/dashboard/components/ui/Button.tsx +1 -1
  73. package/apps/dashboard/components/ui/Input.tsx +1 -1
  74. package/apps/dashboard/components.json +1 -1
  75. package/apps/dashboard/contexts/ClaudeSessionContext.tsx +611 -358
  76. package/apps/dashboard/contexts/ConnectionStatusContext.tsx +0 -1
  77. package/apps/dashboard/contexts/UsageContext.tsx +62 -31
  78. package/apps/dashboard/dev.sh +35 -0
  79. package/apps/dashboard/eslint.config.mjs +9 -9
  80. package/apps/dashboard/hooks/useWebSocket.ts +138 -83
  81. package/apps/dashboard/index.html +73 -0
  82. package/apps/dashboard/lib/data-bridge.ts +722 -0
  83. package/apps/dashboard/lib/db.ts +69 -1302
  84. package/apps/dashboard/lib/environment-config.ts +173 -0
  85. package/apps/dashboard/lib/environment-verification.ts +119 -0
  86. package/apps/dashboard/lib/kanban-utils.ts +226 -26
  87. package/apps/dashboard/lib/proof-run.ts +495 -0
  88. package/apps/dashboard/lib/proof-scenario-runner.ts +346 -0
  89. package/apps/dashboard/lib/service-recovery.ts +326 -0
  90. package/apps/dashboard/lib/session-state-machine.ts +1 -0
  91. package/apps/dashboard/lib/session-state-utils.ts +0 -164
  92. package/apps/dashboard/lib/session-stream-manager.ts +253 -122
  93. package/apps/dashboard/lib/stream-manager-registry.ts +46 -6
  94. package/apps/dashboard/lib/tauri-bridge.ts +102 -0
  95. package/apps/dashboard/lib/tauri.ts +106 -0
  96. package/apps/dashboard/lib/utils.ts +3 -3
  97. package/apps/dashboard/next-env.d.ts +1 -1
  98. package/apps/dashboard/package.json +21 -33
  99. package/apps/dashboard/public/bug-icon.png +0 -0
  100. package/apps/dashboard/public/buoy-icon.png +0 -0
  101. package/apps/dashboard/public/in-flight-seagull.png +0 -0
  102. package/apps/dashboard/public/pier-icon.png +0 -0
  103. package/apps/dashboard/public/star-icon.png +0 -0
  104. package/apps/dashboard/public/wrench-icon.png +0 -0
  105. package/apps/dashboard/scripts/tauri-build.js +228 -0
  106. package/apps/dashboard/scripts/upload-tauri-to-r2.js +125 -0
  107. package/apps/dashboard/src/main.tsx +12 -0
  108. package/apps/dashboard/src/router.tsx +107 -0
  109. package/apps/dashboard/src/vite-env.d.ts +1 -0
  110. package/apps/dashboard/tsconfig.json +7 -12
  111. package/apps/dashboard/tsconfig.tsbuildinfo +1 -1
  112. package/apps/dashboard/vite.config.ts +33 -0
  113. package/apps/update-server/src/index.ts +167 -30
  114. package/claude-hooks/global-guardrails.js +14 -13
  115. package/crates/jettypod-cli/Cargo.toml +19 -0
  116. package/crates/jettypod-cli/src/commands.rs +1249 -0
  117. package/crates/jettypod-cli/src/main.rs +595 -0
  118. package/crates/jettypod-core/Cargo.toml +26 -0
  119. package/crates/jettypod-core/build.rs +98 -0
  120. package/crates/jettypod-core/migrations/V1__baseline.sql +197 -0
  121. package/crates/jettypod-core/migrations/V2__work_items_indexes.sql +6 -0
  122. package/crates/jettypod-core/migrations/V3__qa_steps.sql +2 -0
  123. package/crates/jettypod-core/src/auth.rs +294 -0
  124. package/crates/jettypod-core/src/config.rs +397 -0
  125. package/crates/jettypod-core/src/db/mod.rs +507 -0
  126. package/crates/jettypod-core/src/db/recovery.rs +114 -0
  127. package/crates/jettypod-core/src/db/startup.rs +101 -0
  128. package/crates/jettypod-core/src/db/validate.rs +149 -0
  129. package/crates/jettypod-core/src/error.rs +76 -0
  130. package/crates/jettypod-core/src/git.rs +458 -0
  131. package/crates/jettypod-core/src/lib.rs +20 -0
  132. package/crates/jettypod-core/src/sessions.rs +625 -0
  133. package/crates/jettypod-core/src/skills.rs +556 -0
  134. package/crates/jettypod-core/src/work.rs +1086 -0
  135. package/crates/jettypod-core/src/worktree.rs +628 -0
  136. package/crates/jettypod-core/src/ws.rs +767 -0
  137. package/cucumber-test.cjs +6 -0
  138. package/jettypod.js +96 -4
  139. package/lib/bdd-preflight.js +96 -0
  140. package/lib/merge-lock.js +111 -253
  141. package/lib/migrations/030-rejection-round-columns.js +54 -0
  142. package/lib/migrations/031-session-isolation-index.js +17 -0
  143. package/lib/work-commands/index.js +58 -16
  144. package/lib/work-tracking/index.js +108 -8
  145. package/package.json +1 -1
  146. package/skills-templates/bug-mode/SKILL.md +43 -1
  147. package/skills-templates/chore-mode/SKILL.md +40 -1
  148. package/skills-templates/design-system-selection/SKILL.md +273 -0
  149. package/skills-templates/epic-planning/SKILL.md +14 -0
  150. package/skills-templates/feature-planning/SKILL.md +90 -1
  151. package/skills-templates/production-mode/SKILL.md +20 -0
  152. package/skills-templates/simple-improvement/SKILL.md +39 -2
  153. package/skills-templates/speed-mode/SKILL.md +10 -15
  154. package/skills-templates/stable-mode/SKILL.md +47 -0
  155. package/apps/dashboard/README.md +0 -36
  156. package/apps/dashboard/app/api/claude/[workItemId]/message/route.ts +0 -446
  157. package/apps/dashboard/app/api/claude/[workItemId]/pin/route.ts +0 -24
  158. package/apps/dashboard/app/api/claude/[workItemId]/route.ts +0 -280
  159. package/apps/dashboard/app/api/claude/sessions/[sessionId]/content/route.ts +0 -52
  160. package/apps/dashboard/app/api/claude/sessions/[sessionId]/message/route.ts +0 -525
  161. package/apps/dashboard/app/api/claude/sessions/[sessionId]/pin/route.ts +0 -24
  162. package/apps/dashboard/app/api/claude/sessions/cleanup/route.ts +0 -34
  163. package/apps/dashboard/app/api/claude/sessions/route.ts +0 -184
  164. package/apps/dashboard/app/api/decisions/[id]/route.ts +0 -25
  165. package/apps/dashboard/app/api/internal/set-project/route.ts +0 -17
  166. package/apps/dashboard/app/api/kanban/route.ts +0 -15
  167. package/apps/dashboard/app/api/settings/env-vars/route.ts +0 -125
  168. package/apps/dashboard/app/api/settings/general/route.ts +0 -21
  169. package/apps/dashboard/app/api/tests/route.ts +0 -9
  170. package/apps/dashboard/app/api/tests/run/route.ts +0 -82
  171. package/apps/dashboard/app/api/tests/run/stream/route.ts +0 -71
  172. package/apps/dashboard/app/api/tests/undefined/route.ts +0 -9
  173. package/apps/dashboard/app/api/usage/route.ts +0 -17
  174. package/apps/dashboard/app/api/work/[id]/description/route.ts +0 -21
  175. package/apps/dashboard/app/api/work/[id]/epic/route.ts +0 -21
  176. package/apps/dashboard/app/api/work/[id]/order/route.ts +0 -21
  177. package/apps/dashboard/app/api/work/[id]/route.ts +0 -35
  178. package/apps/dashboard/app/api/work/[id]/status/route.ts +0 -63
  179. package/apps/dashboard/app/api/work/[id]/title/route.ts +0 -21
  180. package/apps/dashboard/app/layout.tsx +0 -55
  181. package/apps/dashboard/components/UpgradeBanner.tsx +0 -30
  182. package/apps/dashboard/electron/ipc-handlers.js +0 -1026
  183. package/apps/dashboard/electron/main.js +0 -2306
  184. package/apps/dashboard/electron/preload.js +0 -125
  185. package/apps/dashboard/electron/session-manager.js +0 -163
  186. package/apps/dashboard/electron-builder.config.js +0 -357
  187. package/apps/dashboard/hooks/useClaudeSessions.ts +0 -299
  188. package/apps/dashboard/lib/backlog-parser.ts +0 -50
  189. package/apps/dashboard/lib/claude-process-manager.ts +0 -529
  190. package/apps/dashboard/lib/db-bridge.ts +0 -283
  191. package/apps/dashboard/lib/prototypes.ts +0 -202
  192. package/apps/dashboard/lib/test-results-db.ts +0 -307
  193. package/apps/dashboard/lib/tests.ts +0 -282
  194. package/apps/dashboard/next.config.js +0 -66
  195. package/apps/dashboard/postcss.config.mjs +0 -7
  196. package/apps/dashboard/public/bug-icon.svg +0 -9
  197. package/apps/dashboard/public/buoy-icon.svg +0 -9
  198. package/apps/dashboard/public/file.svg +0 -1
  199. package/apps/dashboard/public/globe.svg +0 -1
  200. package/apps/dashboard/public/in-flight-seagull.svg +0 -9
  201. package/apps/dashboard/public/next.svg +0 -1
  202. package/apps/dashboard/public/pier-icon.svg +0 -14
  203. package/apps/dashboard/public/star-icon.svg +0 -9
  204. package/apps/dashboard/public/vercel.svg +0 -1
  205. package/apps/dashboard/public/window.svg +0 -1
  206. package/apps/dashboard/public/wrench-icon.svg +0 -9
  207. package/apps/dashboard/scripts/download-node.js +0 -104
  208. package/apps/dashboard/scripts/upload-to-r2.js +0 -89
@@ -1,283 +0,0 @@
1
- // Database bridge - routes calls to IPC (Electron) or direct db (web)
2
- // This allows the same API route code to work in both environments
3
-
4
- import * as directDb from './db';
5
-
6
- // Type for recent project
7
- export interface RecentProject {
8
- name: string;
9
- path: string;
10
- lastOpened?: string;
11
- }
12
-
13
- // Type for the Electron API exposed via preload
14
- interface ElectronAPI {
15
- project: {
16
- openDialog: () => Promise<{
17
- success: boolean;
18
- canceled?: boolean;
19
- error?: string;
20
- path?: string;
21
- }>;
22
- newProject: () => Promise<{
23
- success: boolean;
24
- canceled?: boolean;
25
- error?: string;
26
- path?: string;
27
- }>;
28
- getRecent: () => Promise<RecentProject[]>;
29
- openRecent: (path: string) => Promise<{
30
- success: boolean;
31
- error?: string;
32
- path?: string;
33
- }>;
34
- };
35
- claudeCode: {
36
- install: () => Promise<{
37
- success: boolean;
38
- error?: string;
39
- }>;
40
- isInstalled: () => Promise<boolean>;
41
- isAuthenticated: () => Promise<boolean>;
42
- login: () => Promise<{
43
- success: boolean;
44
- error?: string;
45
- }>;
46
- update: () => Promise<{
47
- success: boolean;
48
- error?: string;
49
- output?: string;
50
- }>;
51
- };
52
- access: {
53
- validate: (code: string) => Promise<{
54
- success: boolean;
55
- error?: string;
56
- }>;
57
- getStatus: () => Promise<{
58
- activated: boolean;
59
- activatedAt?: string;
60
- }>;
61
- };
62
- auth: {
63
- loginWithGoogle: () => Promise<void>;
64
- saveToken: (token: string, user: { email: string; plan?: string }) => Promise<void>;
65
- getStatus: () => Promise<{
66
- authenticated: boolean;
67
- token?: string;
68
- user?: { email: string; plan?: string };
69
- }>;
70
- getToken: () => Promise<string | null>;
71
- logout: () => Promise<void>;
72
- getPostLoginPath: () => Promise<string | null>;
73
- };
74
- subscription: {
75
- createCheckout: (plan: string) => Promise<{ success: boolean; error?: string }>;
76
- activate: (customerId: string) => Promise<{ success: boolean; error?: string }>;
77
- getStatus: () => Promise<{ active: boolean; customerId?: string; activatedAt?: string }>;
78
- };
79
- billing: {
80
- openCustomerPortal: () => Promise<{ success: boolean; error?: string }>;
81
- };
82
- shell: {
83
- openPath: (filePath: string) => Promise<string>;
84
- };
85
- db: {
86
- getKanbanData: (doneLimit?: number) => Promise<ReturnType<typeof directDb.getKanbanData>>;
87
- getAllWorkItems: () => Promise<ReturnType<typeof directDb.getAllWorkItems>>;
88
- getWorkItem: (id: number) => Promise<ReturnType<typeof directDb.getWorkItem>>;
89
- getChildWorkItems: (parentId: number) => Promise<ReturnType<typeof directDb.getChildWorkItems>>;
90
- updateWorkItemTitle: (id: number, title: string) => Promise<boolean>;
91
- updateWorkItemStatus: (id: number, status: string) => Promise<boolean>;
92
- updateWorkItemOrder: (id: number, displayOrder: number) => Promise<boolean>;
93
- updateWorkItemEpic: (id: number, epicId: number | null) => Promise<boolean>;
94
- getDecision: (id: number) => Promise<ReturnType<typeof directDb.getDecision>>;
95
- getDecisionsForWorkItem: (workItemId: number) => Promise<ReturnType<typeof directDb.getDecisionsForWorkItem>>;
96
- listSessions: () => Promise<ReturnType<typeof directDb.listSessions>>;
97
- createSession: (title: string) => Promise<number>;
98
- getSession: (sessionId: number) => Promise<ReturnType<typeof directDb.getSession>>;
99
- closeSession: (sessionId: number) => Promise<boolean>;
100
- closeSessionByWorkItem: (workItemId: number) => Promise<boolean>;
101
- linkSession: (sessionId: number, workItemId: number) => Promise<ReturnType<typeof directDb.linkSession>>;
102
- isLinkableWorkItem: (workItemId: number) => Promise<ReturnType<typeof directDb.isLinkableWorkItem>>;
103
- getActiveSessionByWorkItem: (workItemId: number) => Promise<ReturnType<typeof directDb.getActiveSessionByWorkItem>>;
104
- getOrCreateSessionForWorkItem: (workItemId: number) => Promise<ReturnType<typeof directDb.getOrCreateSessionForWorkItem>>;
105
- countActiveSessions: () => Promise<number>;
106
- cleanupStaleSessions: (retentionDays?: number) => Promise<number>;
107
- getSessionContent: (sessionId: number) => Promise<ReturnType<typeof directDb.getSessionContent>>;
108
- getSessionContentByWorkItem: (workItemId: number) => Promise<ReturnType<typeof directDb.getSessionContentByWorkItem>>;
109
- appendSessionContent: (sessionId: number, turn: directDb.ConversationTurn) => Promise<boolean>;
110
- appendSessionContentByWorkItem: (workItemId: number, turn: directDb.ConversationTurn) => Promise<boolean>;
111
- getEnvVars: () => Promise<ReturnType<typeof directDb.getEnvVars>>;
112
- setEnvVar: (name: string, value: string) => Promise<void>;
113
- deleteEnvVar: (name: string) => Promise<void>;
114
- getProjectName: () => Promise<string>;
115
- };
116
- isElectron: boolean;
117
- }
118
-
119
- declare global {
120
- interface Window {
121
- electronAPI?: ElectronAPI;
122
- }
123
- }
124
-
125
- // Detect if running in Electron renderer process
126
- export function isElectron(): boolean {
127
- // Server-side (API routes in Next.js) - check process
128
- if (typeof window === 'undefined') {
129
- // Check if we're in the Electron main process context
130
- // This won't be true for Next.js API routes even in Electron
131
- // because they run in a separate Node.js context
132
- return false;
133
- }
134
- // Client-side - check for exposed API
135
- return !!(window.electronAPI?.isElectron);
136
- }
137
-
138
- // For server-side code (API routes), we need a different detection mechanism
139
- // Since API routes run in the Next.js server, not the Electron renderer,
140
- // we use an environment variable set by the Electron main process
141
- export function isElectronServer(): boolean {
142
- return process.env.ELECTRON_RUN_AS_NODE === '1' || process.env.IS_ELECTRON === '1';
143
- }
144
-
145
- // Re-export types from db
146
- export type {
147
- WorkItem,
148
- Decision,
149
- KanbanGroup,
150
- InFlightItem,
151
- KanbanData,
152
- ClaudeSession,
153
- ConversationTurn,
154
- SessionWithFeature,
155
- SessionStatus,
156
- EnvVar,
157
- WeeklyUsage,
158
- } from './db';
159
-
160
- // ==================== Kanban ====================
161
- export function getKanbanData(doneLimit?: number): ReturnType<typeof directDb.getKanbanData> {
162
- // Server-side always uses direct db (API routes)
163
- return directDb.getKanbanData(doneLimit);
164
- }
165
-
166
- // ==================== Work Items ====================
167
- export function getAllWorkItems(): ReturnType<typeof directDb.getAllWorkItems> {
168
- return directDb.getAllWorkItems();
169
- }
170
-
171
- export function getWorkItem(id: number): ReturnType<typeof directDb.getWorkItem> {
172
- return directDb.getWorkItem(id);
173
- }
174
-
175
- export function getChildWorkItems(parentId: number): ReturnType<typeof directDb.getChildWorkItems> {
176
- return directDb.getChildWorkItems(parentId);
177
- }
178
-
179
- export function updateWorkItemTitle(id: number, title: string): boolean {
180
- return directDb.updateWorkItemTitle(id, title);
181
- }
182
-
183
- export function updateWorkItemStatus(id: number, status: string): boolean {
184
- return directDb.updateWorkItemStatus(id, status);
185
- }
186
-
187
- export function updateWorkItemOrder(id: number, displayOrder: number): boolean {
188
- return directDb.updateWorkItemOrder(id, displayOrder);
189
- }
190
-
191
- export function updateWorkItemEpic(id: number, epicId: number | null): boolean {
192
- return directDb.updateWorkItemEpic(id, epicId);
193
- }
194
-
195
- // ==================== Decisions ====================
196
- export function getDecision(id: number): ReturnType<typeof directDb.getDecision> {
197
- return directDb.getDecision(id);
198
- }
199
-
200
- export function getDecisionsForWorkItem(workItemId: number): ReturnType<typeof directDb.getDecisionsForWorkItem> {
201
- return directDb.getDecisionsForWorkItem(workItemId);
202
- }
203
-
204
- // ==================== Sessions ====================
205
- export function listSessions(): ReturnType<typeof directDb.listSessions> {
206
- return directDb.listSessions();
207
- }
208
-
209
- export function createSession(title: string): number {
210
- return directDb.createSession(title);
211
- }
212
-
213
- export function getSession(sessionId: number): ReturnType<typeof directDb.getSession> {
214
- return directDb.getSession(sessionId);
215
- }
216
-
217
- export function closeSession(sessionId: number): boolean {
218
- return directDb.closeSession(sessionId);
219
- }
220
-
221
- export function closeSessionByWorkItem(workItemId: number): boolean {
222
- return directDb.closeSessionByWorkItem(workItemId);
223
- }
224
-
225
- export function linkSession(sessionId: number, workItemId: number): ReturnType<typeof directDb.linkSession> {
226
- return directDb.linkSession(sessionId, workItemId);
227
- }
228
-
229
- export function isLinkableWorkItem(workItemId: number): ReturnType<typeof directDb.isLinkableWorkItem> {
230
- return directDb.isLinkableWorkItem(workItemId);
231
- }
232
-
233
- export function getActiveSessionByWorkItem(workItemId: number): ReturnType<typeof directDb.getActiveSessionByWorkItem> {
234
- return directDb.getActiveSessionByWorkItem(workItemId);
235
- }
236
-
237
- export function getOrCreateSessionForWorkItem(workItemId: number): ReturnType<typeof directDb.getOrCreateSessionForWorkItem> {
238
- return directDb.getOrCreateSessionForWorkItem(workItemId);
239
- }
240
-
241
- export function countActiveSessions(): number {
242
- return directDb.countActiveSessions();
243
- }
244
-
245
- export function cleanupStaleSessions(retentionDays?: number): number {
246
- return directDb.cleanupStaleSessions(retentionDays);
247
- }
248
-
249
- // ==================== Session Content ====================
250
- export function getSessionContent(sessionId: number): ReturnType<typeof directDb.getSessionContent> {
251
- return directDb.getSessionContent(sessionId);
252
- }
253
-
254
- export function getSessionContentByWorkItem(workItemId: number): ReturnType<typeof directDb.getSessionContentByWorkItem> {
255
- return directDb.getSessionContentByWorkItem(workItemId);
256
- }
257
-
258
- export function appendSessionContent(sessionId: number, turn: directDb.ConversationTurn): boolean {
259
- return directDb.appendSessionContent(sessionId, turn);
260
- }
261
-
262
- export function appendSessionContentByWorkItem(workItemId: number, turn: directDb.ConversationTurn): boolean {
263
- return directDb.appendSessionContentByWorkItem(workItemId, turn);
264
- }
265
-
266
- // ==================== Environment Variables ====================
267
- export function getEnvVars(): ReturnType<typeof directDb.getEnvVars> {
268
- return directDb.getEnvVars();
269
- }
270
-
271
- export function setEnvVar(name: string, value: string): void {
272
- return directDb.setEnvVar(name, value);
273
- }
274
-
275
- export function deleteEnvVar(name: string): void {
276
- return directDb.deleteEnvVar(name);
277
- }
278
-
279
- // ==================== Project Info ====================
280
- export function getProjectName(): string {
281
- return directDb.getProjectName();
282
- }
283
-
@@ -1,202 +0,0 @@
1
- // Prototype data layer for the prototype browser
2
- // Scans prototypes directory and returns structured metadata
3
-
4
- import path from 'path';
5
- import fs from 'fs';
6
- import { execSync } from 'child_process';
7
-
8
- export interface PrototypeFile {
9
- name: string;
10
- type: string;
11
- path: string;
12
- }
13
-
14
- export interface Prototype {
15
- id: string;
16
- title: string;
17
- date: string;
18
- month: string;
19
- description: string;
20
- feature: { id: number; title: string } | null;
21
- files: PrototypeFile[];
22
- path: string;
23
- }
24
-
25
- export interface PrototypeSummary {
26
- total: number;
27
- byMonth: Record<string, number>;
28
- }
29
-
30
- export interface PrototypeDashboardData {
31
- prototypes: Prototype[];
32
- summary: PrototypeSummary;
33
- }
34
-
35
- function getProjectRoot(): string {
36
- if (process.env.JETTYPOD_PROJECT_PATH) {
37
- return process.env.JETTYPOD_PROJECT_PATH;
38
- }
39
- try {
40
- return execSync('git rev-parse --show-toplevel', { encoding: 'utf-8' }).trim();
41
- } catch {
42
- throw new Error('Not in a git repository and JETTYPOD_PROJECT_PATH not set');
43
- }
44
- }
45
-
46
- // Parse date from filename or folder name
47
- // Formats: "2025-01-11-name" or "feature-123-name"
48
- function parseDate(name: string): Date | null {
49
- // Try YYYY-MM-DD prefix
50
- const dateMatch = name.match(/^(\d{4})-(\d{2})-(\d{2})/);
51
- if (dateMatch) {
52
- return new Date(parseInt(dateMatch[1]), parseInt(dateMatch[2]) - 1, parseInt(dateMatch[3]));
53
- }
54
- return null;
55
- }
56
-
57
- // Parse feature ID from folder name
58
- // Format: "feature-123-name"
59
- function parseFeatureId(name: string): number | null {
60
- const featureMatch = name.match(/^feature-(\d+)-/);
61
- if (featureMatch) {
62
- return parseInt(featureMatch[1]);
63
- }
64
- return null;
65
- }
66
-
67
- // Format date as "January 2026"
68
- function formatMonth(date: Date): string {
69
- return date.toLocaleDateString('en-US', { month: 'long', year: 'numeric' });
70
- }
71
-
72
- // Format date as "2026-01-27"
73
- function formatDate(date: Date): string {
74
- return date.toISOString().split('T')[0];
75
- }
76
-
77
- // Convert slug to title
78
- function slugToTitle(slug: string): string {
79
- // Remove date prefix if present
80
- let title = slug.replace(/^\d{4}-\d{2}-\d{2}-/, '');
81
- // Remove feature-id- prefix if present
82
- title = title.replace(/^feature-\d+-/, '');
83
- // Remove epic- prefix if present
84
- title = title.replace(/^epic-/, '');
85
- // Convert kebab-case to Title Case
86
- return title
87
- .split('-')
88
- .map(word => word.charAt(0).toUpperCase() + word.slice(1))
89
- .join(' ');
90
- }
91
-
92
- // Get file extension type
93
- function getFileType(filename: string): string {
94
- const ext = path.extname(filename).toLowerCase().slice(1);
95
- return ext || 'unknown';
96
- }
97
-
98
- // Get files in a directory
99
- function getFilesInDir(dirPath: string): PrototypeFile[] {
100
- const files: PrototypeFile[] = [];
101
- const entries = fs.readdirSync(dirPath, { withFileTypes: true });
102
-
103
- for (const entry of entries) {
104
- if (entry.isFile() && !entry.name.startsWith('.')) {
105
- files.push({
106
- name: entry.name,
107
- type: getFileType(entry.name),
108
- path: path.join(dirPath, entry.name),
109
- });
110
- }
111
- }
112
-
113
- return files;
114
- }
115
-
116
- // Get modification time for a file or directory
117
- function getModTime(itemPath: string): Date {
118
- const stats = fs.statSync(itemPath);
119
- return stats.mtime;
120
- }
121
-
122
- // Main function to get prototype dashboard data
123
- export function getPrototypeDashboardData(): PrototypeDashboardData {
124
- const projectRoot = getProjectRoot();
125
- const prototypesDir = path.join(projectRoot, 'prototypes');
126
-
127
- if (!fs.existsSync(prototypesDir)) {
128
- return {
129
- prototypes: [],
130
- summary: { total: 0, byMonth: {} },
131
- };
132
- }
133
-
134
- const prototypes: Prototype[] = [];
135
- const entries = fs.readdirSync(prototypesDir, { withFileTypes: true });
136
-
137
- for (const entry of entries) {
138
- if (entry.name.startsWith('.')) continue;
139
-
140
- const itemPath = path.join(prototypesDir, entry.name);
141
-
142
- if (entry.isDirectory()) {
143
- // Directory prototype (feature-xxx-name or topic-name)
144
- const files = getFilesInDir(itemPath);
145
- if (files.length === 0) continue;
146
-
147
- const featureId = parseFeatureId(entry.name);
148
- let date = parseDate(entry.name);
149
-
150
- // If no date in name, use modification time
151
- if (!date) {
152
- date = getModTime(itemPath);
153
- }
154
-
155
- prototypes.push({
156
- id: entry.name,
157
- title: slugToTitle(entry.name),
158
- date: formatDate(date),
159
- month: formatMonth(date),
160
- description: `${files.length} file${files.length > 1 ? 's' : ''}: ${files.map(f => f.name).join(', ')}`,
161
- feature: featureId ? { id: featureId, title: `Feature #${featureId}` } : null,
162
- files,
163
- path: itemPath,
164
- });
165
- } else if (entry.isFile()) {
166
- // Single file prototype (2025-01-11-name.js)
167
- const date = parseDate(entry.name) || getModTime(itemPath);
168
-
169
- prototypes.push({
170
- id: entry.name,
171
- title: slugToTitle(entry.name.replace(/\.[^.]+$/, '')),
172
- date: formatDate(date),
173
- month: formatMonth(date),
174
- description: `Single file: ${entry.name}`,
175
- feature: null,
176
- files: [{
177
- name: entry.name,
178
- type: getFileType(entry.name),
179
- path: itemPath,
180
- }],
181
- path: itemPath,
182
- });
183
- }
184
- }
185
-
186
- // Sort by date descending (newest first)
187
- prototypes.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime());
188
-
189
- // Calculate summary
190
- const byMonth: Record<string, number> = {};
191
- for (const proto of prototypes) {
192
- byMonth[proto.month] = (byMonth[proto.month] || 0) + 1;
193
- }
194
-
195
- return {
196
- prototypes,
197
- summary: {
198
- total: prototypes.length,
199
- byMonth,
200
- },
201
- };
202
- }