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,299 +0,0 @@
1
- 'use client';
2
-
3
- import { useState, useCallback, useRef, useEffect } from 'react';
4
- import type { ClaudeMessage, StreamStatus } from '../lib/session-stream-manager';
5
-
6
- export interface ClaudeSession {
7
- workItemId: string;
8
- title: string;
9
- description?: string;
10
- messages: ClaudeMessage[];
11
- status: StreamStatus;
12
- error: string | null;
13
- exitCode: number | null;
14
- canRetry: boolean;
15
- abortController: AbortController | null;
16
- }
17
-
18
- interface UseClaudeSessionsReturn {
19
- sessions: Map<string, ClaudeSession>;
20
- activeSessionId: string | null;
21
- activeSession: ClaudeSession | null;
22
- startSession: (workItemId: string, title: string, description?: string) => void;
23
- switchSession: (workItemId: string) => void;
24
- stopSession: (workItemId: string) => void;
25
- retrySession: (workItemId: string) => void;
26
- closeSession: (workItemId: string, dbSessionId?: number) => Promise<void>;
27
- closeAllSessions: () => void;
28
- isSessionRunning: (workItemId: string) => boolean;
29
- }
30
-
31
- export function useClaudeSessions(): UseClaudeSessionsReturn {
32
- const [sessions, setSessions] = useState<Map<string, ClaudeSession>>(new Map());
33
- const [activeSessionId, setActiveSessionId] = useState<string | null>(null);
34
- const sessionsRef = useRef<Map<string, ClaudeSession>>(new Map());
35
-
36
- // Keep ref in sync with state for cleanup
37
- useEffect(() => {
38
- sessionsRef.current = sessions;
39
- }, [sessions]);
40
-
41
- // Cleanup all sessions on unmount (browser close/refresh)
42
- useEffect(() => {
43
- return () => {
44
- sessionsRef.current.forEach((session) => {
45
- if (session.abortController) {
46
- session.abortController.abort();
47
- }
48
- });
49
- };
50
- }, []);
51
-
52
- const updateSession = useCallback((workItemId: string, updates: Partial<ClaudeSession>) => {
53
- setSessions((prev) => {
54
- const newMap = new Map(prev);
55
- const session = newMap.get(workItemId);
56
- if (session) {
57
- newMap.set(workItemId, { ...session, ...updates });
58
- }
59
- return newMap;
60
- });
61
- }, []);
62
-
63
- const startSession = useCallback(async (workItemId: string, title: string, description?: string) => {
64
- // Check if session already exists and is running
65
- const existingSession = sessionsRef.current.get(workItemId);
66
- if (existingSession?.status === 'streaming') {
67
- // Just switch to it
68
- setActiveSessionId(workItemId);
69
- return;
70
- }
71
-
72
- // Create or reset session
73
- const controller = new AbortController();
74
- const newSession: ClaudeSession = {
75
- workItemId,
76
- title,
77
- description,
78
- messages: [],
79
- status: 'connecting',
80
- error: null,
81
- exitCode: null,
82
- canRetry: false,
83
- abortController: controller,
84
- };
85
-
86
- setSessions((prev) => {
87
- const newMap = new Map(prev);
88
- newMap.set(workItemId, newSession);
89
- return newMap;
90
- });
91
- setActiveSessionId(workItemId);
92
-
93
- try {
94
- const response = await fetch(`/api/claude/${workItemId}`, {
95
- method: 'POST',
96
- headers: { 'Content-Type': 'application/json' },
97
- body: JSON.stringify({ title, description }),
98
- signal: controller.signal,
99
- });
100
-
101
- if (!response.ok) {
102
- let errorMessage = `HTTP ${response.status}: ${response.statusText}`;
103
- try {
104
- const errorData = await response.json();
105
- if (errorData.message) {
106
- errorMessage = errorData.message;
107
- }
108
- } catch {
109
- // Response wasn't JSON
110
- }
111
- const retryable = response.status >= 500 || response.status === 503;
112
- updateSession(workItemId, {
113
- status: 'error',
114
- error: errorMessage,
115
- canRetry: retryable,
116
- });
117
- return;
118
- }
119
-
120
- if (!response.body) {
121
- updateSession(workItemId, {
122
- status: 'error',
123
- error: 'Response body is null',
124
- canRetry: true,
125
- });
126
- return;
127
- }
128
-
129
- updateSession(workItemId, { status: 'streaming' });
130
-
131
- const reader = response.body.getReader();
132
- const decoder = new TextDecoder();
133
- let buffer = '';
134
-
135
- while (true) {
136
- const { done, value } = await reader.read();
137
-
138
- if (done) {
139
- updateSession(workItemId, { status: 'done' });
140
- break;
141
- }
142
-
143
- buffer += decoder.decode(value, { stream: true });
144
- const lines = buffer.split('\n');
145
- buffer = lines.pop() || '';
146
-
147
- for (const line of lines) {
148
- if (line.startsWith('data: ')) {
149
- const data = line.slice(6);
150
- try {
151
- const parsed = JSON.parse(data);
152
- const message: ClaudeMessage = {
153
- ...parsed,
154
- timestamp: Date.now(),
155
- };
156
-
157
- setSessions((prev) => {
158
- const newMap = new Map(prev);
159
- const session = newMap.get(workItemId);
160
- if (session) {
161
- newMap.set(workItemId, {
162
- ...session,
163
- messages: [...session.messages, message],
164
- });
165
- }
166
- return newMap;
167
- });
168
-
169
- if (parsed.type === 'done') {
170
- if (parsed.exitCode !== undefined && parsed.exitCode !== 0) {
171
- updateSession(workItemId, {
172
- status: 'error',
173
- exitCode: parsed.exitCode,
174
- error: `Process exited with code ${parsed.exitCode}`,
175
- canRetry: true,
176
- });
177
- } else {
178
- updateSession(workItemId, { status: 'done' });
179
- }
180
- } else if (parsed.type === 'error') {
181
- updateSession(workItemId, {
182
- status: 'error',
183
- error: parsed.content || 'Unknown error',
184
- canRetry: true,
185
- });
186
- }
187
- } catch {
188
- // Non-JSON line, skip
189
- }
190
- }
191
- }
192
- }
193
- } catch (err) {
194
- if (err instanceof Error && err.name === 'AbortError') {
195
- updateSession(workItemId, { status: 'idle' });
196
- } else {
197
- const errorMessage = err instanceof Error ? err.message : 'Unknown error';
198
- const isNetworkError = err instanceof TypeError ||
199
- (err instanceof Error && (
200
- err.message.includes('network') ||
201
- err.message.includes('fetch') ||
202
- err.message.includes('Failed to fetch')
203
- ));
204
-
205
- updateSession(workItemId, {
206
- status: 'error',
207
- error: isNetworkError ? 'Connection lost' : errorMessage,
208
- canRetry: true,
209
- });
210
- }
211
- }
212
- }, [updateSession]);
213
-
214
- const switchSession = useCallback((workItemId: string) => {
215
- if (sessions.has(workItemId)) {
216
- setActiveSessionId(workItemId);
217
- }
218
- }, [sessions]);
219
-
220
- const stopSession = useCallback((workItemId: string) => {
221
- const session = sessions.get(workItemId);
222
- if (session?.abortController) {
223
- session.abortController.abort();
224
- }
225
- updateSession(workItemId, {
226
- status: 'idle',
227
- abortController: null,
228
- });
229
- }, [sessions, updateSession]);
230
-
231
- const retrySession = useCallback((workItemId: string) => {
232
- const session = sessions.get(workItemId);
233
- if (session) {
234
- startSession(workItemId, session.title, session.description);
235
- }
236
- }, [sessions, startSession]);
237
-
238
- const closeSession = useCallback(async (workItemId: string, dbSessionId?: number) => {
239
- const session = sessions.get(workItemId);
240
-
241
- // Abort if running
242
- if (session?.abortController) {
243
- session.abortController.abort();
244
- }
245
-
246
- // Call API to mark as completed in DB (if we have a DB session ID)
247
- if (dbSessionId) {
248
- try {
249
- await fetch(`/api/claude/sessions?sessionId=${dbSessionId}`, {
250
- method: 'DELETE',
251
- });
252
- } catch (error) {
253
- console.error('Failed to close session in DB:', error);
254
- }
255
- }
256
-
257
- // Remove from local state
258
- setSessions((prev) => {
259
- const newMap = new Map(prev);
260
- newMap.delete(workItemId);
261
- return newMap;
262
- });
263
-
264
- // If this was the active session, clear it
265
- if (activeSessionId === workItemId) {
266
- setActiveSessionId(null);
267
- }
268
- }, [sessions, activeSessionId]);
269
-
270
- const closeAllSessions = useCallback(() => {
271
- sessions.forEach((session) => {
272
- if (session.abortController) {
273
- session.abortController.abort();
274
- }
275
- });
276
- setSessions(new Map());
277
- setActiveSessionId(null);
278
- }, [sessions]);
279
-
280
- const isSessionRunning = useCallback((workItemId: string) => {
281
- const session = sessions.get(workItemId);
282
- return session?.status === 'streaming' || session?.status === 'connecting';
283
- }, [sessions]);
284
-
285
- const activeSession = activeSessionId ? sessions.get(activeSessionId) ?? null : null;
286
-
287
- return {
288
- sessions,
289
- activeSessionId,
290
- activeSession,
291
- startSession,
292
- switchSession,
293
- stopSession,
294
- retrySession,
295
- closeSession,
296
- closeAllSessions,
297
- isSessionRunning,
298
- };
299
- }
@@ -1,50 +0,0 @@
1
- // Local keyword parser for fast backlog work item creation
2
- // Avoids spawning Claude CLI for obvious inputs
3
-
4
- export interface ParseResult {
5
- type: 'epic' | 'feature' | 'chore' | 'bug';
6
- title: string;
7
- }
8
-
9
- const FILLER_WORDS = /\b(um|uh|like|basically|we need|we should|can you|i want|i need|let's|or something|you know|kind of|sort of|maybe|probably|just|actually)\b/gi;
10
-
11
- const TYPE_PATTERNS: { type: ParseResult['type']; keywords: RegExp }[] = [
12
- { type: 'bug', keywords: /\b(bug|broken|doesn't work|not working|crash|error|regression)\b/i },
13
- { type: 'epic', keywords: /\b(system|full|complete|epic|initiative|project|roadmap)\b/i },
14
- { type: 'chore', keywords: /\b(upgrade|refactor|migrate|update|version|infrastructure|cleanup|clean up|tooling|ci|cd|pipeline|lint|config)\b/i },
15
- // feature is the default — no pattern needed
16
- ];
17
-
18
- export function parseBacklogInput(input: string): ParseResult | null {
19
- const trimmed = input.trim();
20
- if (!trimmed || trimmed.length < 3) return null;
21
-
22
- // Clean up filler words
23
- let cleaned = trimmed.replace(FILLER_WORDS, ' ').replace(/\s+/g, ' ').trim();
24
- if (!cleaned || cleaned.length < 3) return null;
25
-
26
- // Detect type from keywords
27
- let type: ParseResult['type'] = 'feature';
28
- for (const pattern of TYPE_PATTERNS) {
29
- if (pattern.keywords.test(cleaned)) {
30
- type = pattern.type;
31
- break;
32
- }
33
- }
34
-
35
- // Check for comma-separated list → epic
36
- if (cleaned.includes(',') && cleaned.split(',').filter(s => s.trim()).length >= 3) {
37
- type = 'epic';
38
- }
39
-
40
- // Clean title: remove trailing period, capitalize first letter
41
- let title = cleaned.replace(/\.$/, '').trim();
42
- title = title.charAt(0).toUpperCase() + title.slice(1);
43
-
44
- // Truncate to 60 chars
45
- if (title.length > 60) {
46
- title = title.substring(0, 57) + '...';
47
- }
48
-
49
- return { type, title };
50
- }