jettypod 4.4.118 → 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 (240) hide show
  1. package/.env +4 -3
  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 +63 -58
  8. package/apps/dashboard/app/demo/gates/page.tsx +43 -45
  9. package/apps/dashboard/app/design-system/page.tsx +868 -0
  10. package/apps/dashboard/app/globals.css +80 -4
  11. package/apps/dashboard/app/install-claude/page.tsx +4 -6
  12. package/apps/dashboard/app/login/page.tsx +72 -54
  13. package/apps/dashboard/app/page.tsx +101 -48
  14. package/apps/dashboard/app/settings/page.tsx +61 -13
  15. package/apps/dashboard/app/signup/page.tsx +242 -0
  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 +13 -16
  19. package/apps/dashboard/app/work/[id]/page.tsx +117 -118
  20. package/apps/dashboard/app/work/[id]/proof/page.tsx +1489 -0
  21. package/apps/dashboard/components/AppShell.tsx +92 -85
  22. package/apps/dashboard/components/CardMenu.tsx +45 -12
  23. package/apps/dashboard/components/ClaudePanel.tsx +771 -850
  24. package/apps/dashboard/components/ClaudePanelInput.tsx +43 -15
  25. package/apps/dashboard/components/ConnectClaudeScreen.tsx +17 -34
  26. package/apps/dashboard/components/CopyableId.tsx +3 -4
  27. package/apps/dashboard/components/DetailReviewActions.tsx +100 -0
  28. package/apps/dashboard/components/DragContext.tsx +134 -63
  29. package/apps/dashboard/components/DraggableCard.tsx +3 -5
  30. package/apps/dashboard/components/DropZone.tsx +6 -7
  31. package/apps/dashboard/components/EditableDetailDescription.tsx +7 -13
  32. package/apps/dashboard/components/EditableDetailTitle.tsx +6 -13
  33. package/apps/dashboard/components/EditableTitle.tsx +26 -7
  34. package/apps/dashboard/components/ElapsedTimer.tsx +66 -0
  35. package/apps/dashboard/components/EpicGroup.tsx +359 -0
  36. package/apps/dashboard/components/GateCard.tsx +79 -17
  37. package/apps/dashboard/components/GateChoiceCard.tsx +15 -18
  38. package/apps/dashboard/components/InstallClaudeScreen.tsx +15 -32
  39. package/apps/dashboard/components/JettyLoader.tsx +37 -0
  40. package/apps/dashboard/components/KanbanBoard.tsx +368 -958
  41. package/apps/dashboard/components/KanbanCard.tsx +740 -0
  42. package/apps/dashboard/components/LazyCard.tsx +62 -0
  43. package/apps/dashboard/components/LazyMarkdown.tsx +11 -0
  44. package/apps/dashboard/components/MainNav.tsx +38 -73
  45. package/apps/dashboard/components/MessageBlock.tsx +468 -0
  46. package/apps/dashboard/components/ModeStartCard.tsx +15 -16
  47. package/apps/dashboard/components/OnboardingWelcome.tsx +213 -0
  48. package/apps/dashboard/components/PlaceholderCard.tsx +3 -4
  49. package/apps/dashboard/components/ProjectSwitcher.tsx +30 -30
  50. package/apps/dashboard/components/PrototypeTimeline.tsx +72 -51
  51. package/apps/dashboard/components/RealTimeKanbanWrapper.tsx +406 -388
  52. package/apps/dashboard/components/RealTimeTestsWrapper.tsx +373 -235
  53. package/apps/dashboard/components/ReviewFooter.tsx +139 -0
  54. package/apps/dashboard/components/SessionList.tsx +19 -19
  55. package/apps/dashboard/components/SubscribeContent.tsx +91 -47
  56. package/apps/dashboard/components/TestTree.tsx +16 -16
  57. package/apps/dashboard/components/TipCard.tsx +16 -17
  58. package/apps/dashboard/components/Toast.tsx +5 -6
  59. package/apps/dashboard/components/TypeIcon.tsx +55 -0
  60. package/apps/dashboard/components/ViewModeToolbar.tsx +104 -0
  61. package/apps/dashboard/components/WaveCompletionAnimation.tsx +52 -65
  62. package/apps/dashboard/components/WelcomeScreen.tsx +19 -35
  63. package/apps/dashboard/components/WorkItemHeader.tsx +4 -5
  64. package/apps/dashboard/components/WorkItemTree.tsx +11 -32
  65. package/apps/dashboard/components/settings/AccountSection.tsx +55 -35
  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 +74 -152
  69. package/apps/dashboard/components/settings/GeneralSection.tsx +162 -56
  70. package/apps/dashboard/components/settings/ProjectStackSection.tsx +948 -0
  71. package/apps/dashboard/components/settings/SettingsLayout.tsx +4 -5
  72. package/apps/dashboard/components/ui/Button.tsx +104 -0
  73. package/apps/dashboard/components/ui/Input.tsx +78 -0
  74. package/apps/dashboard/components.json +1 -1
  75. package/apps/dashboard/contexts/ClaudeSessionContext.tsx +711 -418
  76. package/apps/dashboard/contexts/ConnectionStatusContext.tsx +25 -5
  77. package/apps/dashboard/contexts/UsageContext.tsx +87 -32
  78. package/apps/dashboard/dev.sh +35 -0
  79. package/apps/dashboard/eslint.config.mjs +9 -9
  80. package/apps/dashboard/hooks/useKanbanAnimation.ts +29 -0
  81. package/apps/dashboard/hooks/useKanbanUndo.ts +83 -0
  82. package/apps/dashboard/hooks/useWebSocket.ts +138 -83
  83. package/apps/dashboard/index.html +73 -0
  84. package/apps/dashboard/lib/constants.ts +43 -0
  85. package/apps/dashboard/lib/data-bridge.ts +722 -0
  86. package/apps/dashboard/lib/db.ts +69 -1265
  87. package/apps/dashboard/lib/environment-config.ts +173 -0
  88. package/apps/dashboard/lib/environment-verification.ts +119 -0
  89. package/apps/dashboard/lib/kanban-utils.ts +270 -0
  90. package/apps/dashboard/lib/proof-run.ts +495 -0
  91. package/apps/dashboard/lib/proof-scenario-runner.ts +346 -0
  92. package/apps/dashboard/lib/run-migrations.js +27 -2
  93. package/apps/dashboard/lib/service-recovery.ts +326 -0
  94. package/apps/dashboard/lib/session-state-machine.ts +1 -0
  95. package/apps/dashboard/lib/session-state-utils.ts +0 -164
  96. package/apps/dashboard/lib/session-stream-manager.ts +308 -134
  97. package/apps/dashboard/lib/shadows.ts +7 -0
  98. package/apps/dashboard/lib/stream-manager-registry.ts +46 -6
  99. package/apps/dashboard/lib/tauri-bridge.ts +102 -0
  100. package/apps/dashboard/lib/tauri.ts +106 -0
  101. package/apps/dashboard/lib/utils.ts +6 -0
  102. package/apps/dashboard/next-env.d.ts +1 -1
  103. package/apps/dashboard/package.json +21 -32
  104. package/apps/dashboard/public/bug-icon.png +0 -0
  105. package/apps/dashboard/public/buoy-icon.png +0 -0
  106. package/apps/dashboard/public/fonts/Satoshi-Variable.woff2 +0 -0
  107. package/apps/dashboard/public/fonts/Satoshi-VariableItalic.woff2 +0 -0
  108. package/apps/dashboard/public/in-flight-seagull.png +0 -0
  109. package/apps/dashboard/public/jetty-icon-loading-alt.svg +11 -0
  110. package/apps/dashboard/public/jetty-icon-loading.svg +11 -0
  111. package/apps/dashboard/public/jettypod_logo.png +0 -0
  112. package/apps/dashboard/public/pier-icon.png +0 -0
  113. package/apps/dashboard/public/star-icon.png +0 -0
  114. package/apps/dashboard/public/wrench-icon.png +0 -0
  115. package/apps/dashboard/scripts/tauri-build.js +228 -0
  116. package/apps/dashboard/scripts/upload-tauri-to-r2.js +125 -0
  117. package/apps/dashboard/scripts/ws-server.js +191 -0
  118. package/apps/dashboard/src/main.tsx +12 -0
  119. package/apps/dashboard/src/router.tsx +107 -0
  120. package/apps/dashboard/src/vite-env.d.ts +1 -0
  121. package/apps/dashboard/tsconfig.json +7 -12
  122. package/apps/dashboard/tsconfig.tsbuildinfo +1 -1
  123. package/apps/dashboard/vite.config.ts +33 -0
  124. package/apps/update-server/src/index.ts +228 -80
  125. package/claude-hooks/global-guardrails.js +14 -13
  126. package/crates/jettypod-cli/Cargo.toml +19 -0
  127. package/crates/jettypod-cli/src/commands.rs +1249 -0
  128. package/crates/jettypod-cli/src/main.rs +595 -0
  129. package/crates/jettypod-core/Cargo.toml +26 -0
  130. package/crates/jettypod-core/build.rs +98 -0
  131. package/crates/jettypod-core/migrations/V1__baseline.sql +197 -0
  132. package/crates/jettypod-core/migrations/V2__work_items_indexes.sql +6 -0
  133. package/crates/jettypod-core/migrations/V3__qa_steps.sql +2 -0
  134. package/crates/jettypod-core/src/auth.rs +294 -0
  135. package/crates/jettypod-core/src/config.rs +397 -0
  136. package/crates/jettypod-core/src/db/mod.rs +507 -0
  137. package/crates/jettypod-core/src/db/recovery.rs +114 -0
  138. package/crates/jettypod-core/src/db/startup.rs +101 -0
  139. package/crates/jettypod-core/src/db/validate.rs +149 -0
  140. package/crates/jettypod-core/src/error.rs +76 -0
  141. package/crates/jettypod-core/src/git.rs +458 -0
  142. package/crates/jettypod-core/src/lib.rs +20 -0
  143. package/crates/jettypod-core/src/sessions.rs +625 -0
  144. package/crates/jettypod-core/src/skills.rs +556 -0
  145. package/crates/jettypod-core/src/work.rs +1086 -0
  146. package/crates/jettypod-core/src/worktree.rs +628 -0
  147. package/crates/jettypod-core/src/ws.rs +767 -0
  148. package/cucumber-test.cjs +6 -0
  149. package/cucumber.js +9 -3
  150. package/docs/COMMAND_REFERENCE.md +34 -0
  151. package/hooks/post-checkout +32 -75
  152. package/hooks/post-merge +111 -10
  153. package/jest.setup.js +1 -0
  154. package/jettypod.js +145 -116
  155. package/lib/bdd-preflight.js +96 -0
  156. package/lib/chore-taxonomy.js +33 -10
  157. package/lib/database.js +36 -16
  158. package/lib/db-watcher.js +1 -1
  159. package/lib/git-hooks/pre-commit +1 -1
  160. package/lib/jettypod-backup.js +27 -4
  161. package/lib/merge-lock.js +111 -253
  162. package/lib/migrations/027-plan-at-creation-column.js +3 -1
  163. package/lib/migrations/029-remove-autoincrement.js +307 -0
  164. package/lib/migrations/029-rename-corrupted-to-cleaned.js +149 -0
  165. package/lib/migrations/030-rejection-round-columns.js +54 -0
  166. package/lib/migrations/031-session-isolation-index.js +17 -0
  167. package/lib/migrations/index.js +47 -4
  168. package/lib/schema.js +10 -5
  169. package/lib/seed-onboarding.js +1 -1
  170. package/lib/update-command/index.js +9 -175
  171. package/lib/work-commands/index.js +144 -19
  172. package/lib/work-tracking/index.js +148 -27
  173. package/lib/worktree-diagnostics.js +16 -16
  174. package/lib/worktree-facade.js +1 -1
  175. package/lib/worktree-manager.js +8 -8
  176. package/lib/worktree-reconciler.js +5 -5
  177. package/package.json +9 -2
  178. package/scripts/ndjson-to-cucumber-json.js +152 -0
  179. package/scripts/postinstall.js +25 -0
  180. package/skills-templates/bug-mode/SKILL.md +79 -20
  181. package/skills-templates/bug-planning/SKILL.md +25 -29
  182. package/skills-templates/chore-mode/SKILL.md +171 -69
  183. package/skills-templates/chore-mode/verification.js +51 -10
  184. package/skills-templates/chore-planning/SKILL.md +47 -18
  185. package/skills-templates/design-system-selection/SKILL.md +273 -0
  186. package/skills-templates/epic-planning/SKILL.md +82 -48
  187. package/skills-templates/external-transition/SKILL.md +47 -47
  188. package/skills-templates/feature-planning/SKILL.md +173 -74
  189. package/skills-templates/production-mode/SKILL.md +69 -49
  190. package/skills-templates/request-routing/SKILL.md +4 -4
  191. package/skills-templates/simple-improvement/SKILL.md +74 -29
  192. package/skills-templates/speed-mode/SKILL.md +217 -141
  193. package/skills-templates/stable-mode/SKILL.md +148 -89
  194. package/apps/dashboard/README.md +0 -36
  195. package/apps/dashboard/app/api/claude/[workItemId]/message/route.ts +0 -386
  196. package/apps/dashboard/app/api/claude/[workItemId]/pin/route.ts +0 -24
  197. package/apps/dashboard/app/api/claude/[workItemId]/route.ts +0 -167
  198. package/apps/dashboard/app/api/claude/sessions/[sessionId]/content/route.ts +0 -52
  199. package/apps/dashboard/app/api/claude/sessions/[sessionId]/message/route.ts +0 -378
  200. package/apps/dashboard/app/api/claude/sessions/[sessionId]/pin/route.ts +0 -24
  201. package/apps/dashboard/app/api/claude/sessions/cleanup/route.ts +0 -34
  202. package/apps/dashboard/app/api/claude/sessions/route.ts +0 -184
  203. package/apps/dashboard/app/api/decisions/[id]/route.ts +0 -25
  204. package/apps/dashboard/app/api/internal/set-project/route.ts +0 -17
  205. package/apps/dashboard/app/api/kanban/route.ts +0 -15
  206. package/apps/dashboard/app/api/settings/env-vars/route.ts +0 -125
  207. package/apps/dashboard/app/api/settings/general/route.ts +0 -21
  208. package/apps/dashboard/app/api/tests/route.ts +0 -9
  209. package/apps/dashboard/app/api/tests/run/route.ts +0 -82
  210. package/apps/dashboard/app/api/tests/run/stream/route.ts +0 -71
  211. package/apps/dashboard/app/api/tests/undefined/route.ts +0 -9
  212. package/apps/dashboard/app/api/usage/route.ts +0 -17
  213. package/apps/dashboard/app/api/work/[id]/description/route.ts +0 -21
  214. package/apps/dashboard/app/api/work/[id]/epic/route.ts +0 -21
  215. package/apps/dashboard/app/api/work/[id]/order/route.ts +0 -21
  216. package/apps/dashboard/app/api/work/[id]/status/route.ts +0 -21
  217. package/apps/dashboard/app/api/work/[id]/title/route.ts +0 -21
  218. package/apps/dashboard/app/layout.tsx +0 -43
  219. package/apps/dashboard/components/UpgradeBanner.tsx +0 -29
  220. package/apps/dashboard/electron/ipc-handlers.js +0 -1028
  221. package/apps/dashboard/electron/main.js +0 -2124
  222. package/apps/dashboard/electron/preload.js +0 -123
  223. package/apps/dashboard/electron/session-manager.js +0 -141
  224. package/apps/dashboard/electron-builder.config.js +0 -357
  225. package/apps/dashboard/hooks/useClaudeSessions.ts +0 -299
  226. package/apps/dashboard/lib/claude-process-manager.ts +0 -492
  227. package/apps/dashboard/lib/db-bridge.ts +0 -282
  228. package/apps/dashboard/lib/prototypes.ts +0 -202
  229. package/apps/dashboard/lib/test-results-db.ts +0 -307
  230. package/apps/dashboard/lib/tests.ts +0 -282
  231. package/apps/dashboard/next.config.js +0 -50
  232. package/apps/dashboard/postcss.config.mjs +0 -7
  233. package/apps/dashboard/public/file.svg +0 -1
  234. package/apps/dashboard/public/globe.svg +0 -1
  235. package/apps/dashboard/public/next.svg +0 -1
  236. package/apps/dashboard/public/vercel.svg +0 -1
  237. package/apps/dashboard/public/window.svg +0 -1
  238. package/apps/dashboard/scripts/download-node.js +0 -104
  239. package/apps/dashboard/scripts/upload-to-r2.js +0 -89
  240. package/docs/bdd-guidance.md +0 -390
@@ -1,282 +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
- };
73
- subscription: {
74
- createCheckout: (plan: string) => Promise<{ success: boolean; error?: string }>;
75
- activate: (customerId: string) => Promise<{ success: boolean; error?: string }>;
76
- getStatus: () => Promise<{ active: boolean; customerId?: string; activatedAt?: string }>;
77
- };
78
- billing: {
79
- openCustomerPortal: () => Promise<{ success: boolean; error?: string }>;
80
- };
81
- shell: {
82
- openPath: (filePath: string) => Promise<string>;
83
- };
84
- db: {
85
- getKanbanData: (doneLimit?: number) => Promise<ReturnType<typeof directDb.getKanbanData>>;
86
- getAllWorkItems: () => Promise<ReturnType<typeof directDb.getAllWorkItems>>;
87
- getWorkItem: (id: number) => Promise<ReturnType<typeof directDb.getWorkItem>>;
88
- getChildWorkItems: (parentId: number) => Promise<ReturnType<typeof directDb.getChildWorkItems>>;
89
- updateWorkItemTitle: (id: number, title: string) => Promise<boolean>;
90
- updateWorkItemStatus: (id: number, status: string) => Promise<boolean>;
91
- updateWorkItemOrder: (id: number, displayOrder: number) => Promise<boolean>;
92
- updateWorkItemEpic: (id: number, epicId: number | null) => Promise<boolean>;
93
- getDecision: (id: number) => Promise<ReturnType<typeof directDb.getDecision>>;
94
- getDecisionsForWorkItem: (workItemId: number) => Promise<ReturnType<typeof directDb.getDecisionsForWorkItem>>;
95
- listSessions: () => Promise<ReturnType<typeof directDb.listSessions>>;
96
- createSession: (title: string) => Promise<number>;
97
- getSession: (sessionId: number) => Promise<ReturnType<typeof directDb.getSession>>;
98
- closeSession: (sessionId: number) => Promise<boolean>;
99
- closeSessionByWorkItem: (workItemId: number) => Promise<boolean>;
100
- linkSession: (sessionId: number, workItemId: number) => Promise<ReturnType<typeof directDb.linkSession>>;
101
- isLinkableWorkItem: (workItemId: number) => Promise<ReturnType<typeof directDb.isLinkableWorkItem>>;
102
- getActiveSessionByWorkItem: (workItemId: number) => Promise<ReturnType<typeof directDb.getActiveSessionByWorkItem>>;
103
- getOrCreateSessionForWorkItem: (workItemId: number) => Promise<ReturnType<typeof directDb.getOrCreateSessionForWorkItem>>;
104
- countActiveSessions: () => Promise<number>;
105
- cleanupStaleSessions: (retentionDays?: number) => Promise<number>;
106
- getSessionContent: (sessionId: number) => Promise<ReturnType<typeof directDb.getSessionContent>>;
107
- getSessionContentByWorkItem: (workItemId: number) => Promise<ReturnType<typeof directDb.getSessionContentByWorkItem>>;
108
- appendSessionContent: (sessionId: number, turn: directDb.ConversationTurn) => Promise<boolean>;
109
- appendSessionContentByWorkItem: (workItemId: number, turn: directDb.ConversationTurn) => Promise<boolean>;
110
- getEnvVars: () => Promise<ReturnType<typeof directDb.getEnvVars>>;
111
- setEnvVar: (name: string, value: string) => Promise<void>;
112
- deleteEnvVar: (name: string) => Promise<void>;
113
- getProjectName: () => Promise<string>;
114
- };
115
- isElectron: boolean;
116
- }
117
-
118
- declare global {
119
- interface Window {
120
- electronAPI?: ElectronAPI;
121
- }
122
- }
123
-
124
- // Detect if running in Electron renderer process
125
- export function isElectron(): boolean {
126
- // Server-side (API routes in Next.js) - check process
127
- if (typeof window === 'undefined') {
128
- // Check if we're in the Electron main process context
129
- // This won't be true for Next.js API routes even in Electron
130
- // because they run in a separate Node.js context
131
- return false;
132
- }
133
- // Client-side - check for exposed API
134
- return !!(window.electronAPI?.isElectron);
135
- }
136
-
137
- // For server-side code (API routes), we need a different detection mechanism
138
- // Since API routes run in the Next.js server, not the Electron renderer,
139
- // we use an environment variable set by the Electron main process
140
- export function isElectronServer(): boolean {
141
- return process.env.ELECTRON_RUN_AS_NODE === '1' || process.env.IS_ELECTRON === '1';
142
- }
143
-
144
- // Re-export types from db
145
- export type {
146
- WorkItem,
147
- Decision,
148
- KanbanGroup,
149
- InFlightItem,
150
- KanbanData,
151
- ClaudeSession,
152
- ConversationTurn,
153
- SessionWithFeature,
154
- SessionStatus,
155
- EnvVar,
156
- WeeklyUsage,
157
- } from './db';
158
-
159
- // ==================== Kanban ====================
160
- export function getKanbanData(doneLimit?: number): ReturnType<typeof directDb.getKanbanData> {
161
- // Server-side always uses direct db (API routes)
162
- return directDb.getKanbanData(doneLimit);
163
- }
164
-
165
- // ==================== Work Items ====================
166
- export function getAllWorkItems(): ReturnType<typeof directDb.getAllWorkItems> {
167
- return directDb.getAllWorkItems();
168
- }
169
-
170
- export function getWorkItem(id: number): ReturnType<typeof directDb.getWorkItem> {
171
- return directDb.getWorkItem(id);
172
- }
173
-
174
- export function getChildWorkItems(parentId: number): ReturnType<typeof directDb.getChildWorkItems> {
175
- return directDb.getChildWorkItems(parentId);
176
- }
177
-
178
- export function updateWorkItemTitle(id: number, title: string): boolean {
179
- return directDb.updateWorkItemTitle(id, title);
180
- }
181
-
182
- export function updateWorkItemStatus(id: number, status: string): boolean {
183
- return directDb.updateWorkItemStatus(id, status);
184
- }
185
-
186
- export function updateWorkItemOrder(id: number, displayOrder: number): boolean {
187
- return directDb.updateWorkItemOrder(id, displayOrder);
188
- }
189
-
190
- export function updateWorkItemEpic(id: number, epicId: number | null): boolean {
191
- return directDb.updateWorkItemEpic(id, epicId);
192
- }
193
-
194
- // ==================== Decisions ====================
195
- export function getDecision(id: number): ReturnType<typeof directDb.getDecision> {
196
- return directDb.getDecision(id);
197
- }
198
-
199
- export function getDecisionsForWorkItem(workItemId: number): ReturnType<typeof directDb.getDecisionsForWorkItem> {
200
- return directDb.getDecisionsForWorkItem(workItemId);
201
- }
202
-
203
- // ==================== Sessions ====================
204
- export function listSessions(): ReturnType<typeof directDb.listSessions> {
205
- return directDb.listSessions();
206
- }
207
-
208
- export function createSession(title: string): number {
209
- return directDb.createSession(title);
210
- }
211
-
212
- export function getSession(sessionId: number): ReturnType<typeof directDb.getSession> {
213
- return directDb.getSession(sessionId);
214
- }
215
-
216
- export function closeSession(sessionId: number): boolean {
217
- return directDb.closeSession(sessionId);
218
- }
219
-
220
- export function closeSessionByWorkItem(workItemId: number): boolean {
221
- return directDb.closeSessionByWorkItem(workItemId);
222
- }
223
-
224
- export function linkSession(sessionId: number, workItemId: number): ReturnType<typeof directDb.linkSession> {
225
- return directDb.linkSession(sessionId, workItemId);
226
- }
227
-
228
- export function isLinkableWorkItem(workItemId: number): ReturnType<typeof directDb.isLinkableWorkItem> {
229
- return directDb.isLinkableWorkItem(workItemId);
230
- }
231
-
232
- export function getActiveSessionByWorkItem(workItemId: number): ReturnType<typeof directDb.getActiveSessionByWorkItem> {
233
- return directDb.getActiveSessionByWorkItem(workItemId);
234
- }
235
-
236
- export function getOrCreateSessionForWorkItem(workItemId: number): ReturnType<typeof directDb.getOrCreateSessionForWorkItem> {
237
- return directDb.getOrCreateSessionForWorkItem(workItemId);
238
- }
239
-
240
- export function countActiveSessions(): number {
241
- return directDb.countActiveSessions();
242
- }
243
-
244
- export function cleanupStaleSessions(retentionDays?: number): number {
245
- return directDb.cleanupStaleSessions(retentionDays);
246
- }
247
-
248
- // ==================== Session Content ====================
249
- export function getSessionContent(sessionId: number): ReturnType<typeof directDb.getSessionContent> {
250
- return directDb.getSessionContent(sessionId);
251
- }
252
-
253
- export function getSessionContentByWorkItem(workItemId: number): ReturnType<typeof directDb.getSessionContentByWorkItem> {
254
- return directDb.getSessionContentByWorkItem(workItemId);
255
- }
256
-
257
- export function appendSessionContent(sessionId: number, turn: directDb.ConversationTurn): boolean {
258
- return directDb.appendSessionContent(sessionId, turn);
259
- }
260
-
261
- export function appendSessionContentByWorkItem(workItemId: number, turn: directDb.ConversationTurn): boolean {
262
- return directDb.appendSessionContentByWorkItem(workItemId, turn);
263
- }
264
-
265
- // ==================== Environment Variables ====================
266
- export function getEnvVars(): ReturnType<typeof directDb.getEnvVars> {
267
- return directDb.getEnvVars();
268
- }
269
-
270
- export function setEnvVar(name: string, value: string): void {
271
- return directDb.setEnvVar(name, value);
272
- }
273
-
274
- export function deleteEnvVar(name: string): void {
275
- return directDb.deleteEnvVar(name);
276
- }
277
-
278
- // ==================== Project Info ====================
279
- export function getProjectName(): string {
280
- return directDb.getProjectName();
281
- }
282
-
@@ -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
- }