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.
- package/.env +4 -3
- package/Cargo.lock +6450 -0
- package/Cargo.toml +35 -0
- package/README.md +5 -1
- package/TAURI-MIGRATION-PLAN.md +840 -0
- package/apps/dashboard/app/connect-claude/page.tsx +5 -6
- package/apps/dashboard/app/decision/[id]/page.tsx +63 -58
- package/apps/dashboard/app/demo/gates/page.tsx +43 -45
- package/apps/dashboard/app/design-system/page.tsx +868 -0
- package/apps/dashboard/app/globals.css +80 -4
- package/apps/dashboard/app/install-claude/page.tsx +4 -6
- package/apps/dashboard/app/login/page.tsx +72 -54
- package/apps/dashboard/app/page.tsx +101 -48
- package/apps/dashboard/app/settings/page.tsx +61 -13
- package/apps/dashboard/app/signup/page.tsx +242 -0
- package/apps/dashboard/app/subscribe/page.tsx +0 -2
- package/apps/dashboard/app/tests/page.tsx +37 -4
- package/apps/dashboard/app/welcome/page.tsx +13 -16
- package/apps/dashboard/app/work/[id]/page.tsx +117 -118
- package/apps/dashboard/app/work/[id]/proof/page.tsx +1489 -0
- package/apps/dashboard/components/AppShell.tsx +92 -85
- package/apps/dashboard/components/CardMenu.tsx +45 -12
- package/apps/dashboard/components/ClaudePanel.tsx +771 -850
- package/apps/dashboard/components/ClaudePanelInput.tsx +43 -15
- package/apps/dashboard/components/ConnectClaudeScreen.tsx +17 -34
- package/apps/dashboard/components/CopyableId.tsx +3 -4
- package/apps/dashboard/components/DetailReviewActions.tsx +100 -0
- package/apps/dashboard/components/DragContext.tsx +134 -63
- package/apps/dashboard/components/DraggableCard.tsx +3 -5
- package/apps/dashboard/components/DropZone.tsx +6 -7
- package/apps/dashboard/components/EditableDetailDescription.tsx +7 -13
- package/apps/dashboard/components/EditableDetailTitle.tsx +6 -13
- package/apps/dashboard/components/EditableTitle.tsx +26 -7
- package/apps/dashboard/components/ElapsedTimer.tsx +66 -0
- package/apps/dashboard/components/EpicGroup.tsx +359 -0
- package/apps/dashboard/components/GateCard.tsx +79 -17
- package/apps/dashboard/components/GateChoiceCard.tsx +15 -18
- package/apps/dashboard/components/InstallClaudeScreen.tsx +15 -32
- package/apps/dashboard/components/JettyLoader.tsx +37 -0
- package/apps/dashboard/components/KanbanBoard.tsx +368 -958
- package/apps/dashboard/components/KanbanCard.tsx +740 -0
- package/apps/dashboard/components/LazyCard.tsx +62 -0
- package/apps/dashboard/components/LazyMarkdown.tsx +11 -0
- package/apps/dashboard/components/MainNav.tsx +38 -73
- package/apps/dashboard/components/MessageBlock.tsx +468 -0
- package/apps/dashboard/components/ModeStartCard.tsx +15 -16
- package/apps/dashboard/components/OnboardingWelcome.tsx +213 -0
- package/apps/dashboard/components/PlaceholderCard.tsx +3 -4
- package/apps/dashboard/components/ProjectSwitcher.tsx +30 -30
- package/apps/dashboard/components/PrototypeTimeline.tsx +72 -51
- package/apps/dashboard/components/RealTimeKanbanWrapper.tsx +406 -388
- package/apps/dashboard/components/RealTimeTestsWrapper.tsx +373 -235
- package/apps/dashboard/components/ReviewFooter.tsx +139 -0
- package/apps/dashboard/components/SessionList.tsx +19 -19
- package/apps/dashboard/components/SubscribeContent.tsx +91 -47
- package/apps/dashboard/components/TestTree.tsx +16 -16
- package/apps/dashboard/components/TipCard.tsx +16 -17
- package/apps/dashboard/components/Toast.tsx +5 -6
- package/apps/dashboard/components/TypeIcon.tsx +55 -0
- package/apps/dashboard/components/ViewModeToolbar.tsx +104 -0
- package/apps/dashboard/components/WaveCompletionAnimation.tsx +52 -65
- package/apps/dashboard/components/WelcomeScreen.tsx +19 -35
- package/apps/dashboard/components/WorkItemHeader.tsx +4 -5
- package/apps/dashboard/components/WorkItemTree.tsx +11 -32
- package/apps/dashboard/components/settings/AccountSection.tsx +55 -35
- package/apps/dashboard/components/settings/AiContextSection.tsx +89 -0
- package/apps/dashboard/components/settings/ContextDocumentsSection.tsx +317 -0
- package/apps/dashboard/components/settings/EnvVarsSection.tsx +74 -152
- package/apps/dashboard/components/settings/GeneralSection.tsx +162 -56
- package/apps/dashboard/components/settings/ProjectStackSection.tsx +948 -0
- package/apps/dashboard/components/settings/SettingsLayout.tsx +4 -5
- package/apps/dashboard/components/ui/Button.tsx +104 -0
- package/apps/dashboard/components/ui/Input.tsx +78 -0
- package/apps/dashboard/components.json +1 -1
- package/apps/dashboard/contexts/ClaudeSessionContext.tsx +711 -418
- package/apps/dashboard/contexts/ConnectionStatusContext.tsx +25 -5
- package/apps/dashboard/contexts/UsageContext.tsx +87 -32
- package/apps/dashboard/dev.sh +35 -0
- package/apps/dashboard/eslint.config.mjs +9 -9
- package/apps/dashboard/hooks/useKanbanAnimation.ts +29 -0
- package/apps/dashboard/hooks/useKanbanUndo.ts +83 -0
- package/apps/dashboard/hooks/useWebSocket.ts +138 -83
- package/apps/dashboard/index.html +73 -0
- package/apps/dashboard/lib/constants.ts +43 -0
- package/apps/dashboard/lib/data-bridge.ts +722 -0
- package/apps/dashboard/lib/db.ts +69 -1265
- package/apps/dashboard/lib/environment-config.ts +173 -0
- package/apps/dashboard/lib/environment-verification.ts +119 -0
- package/apps/dashboard/lib/kanban-utils.ts +270 -0
- package/apps/dashboard/lib/proof-run.ts +495 -0
- package/apps/dashboard/lib/proof-scenario-runner.ts +346 -0
- package/apps/dashboard/lib/run-migrations.js +27 -2
- package/apps/dashboard/lib/service-recovery.ts +326 -0
- package/apps/dashboard/lib/session-state-machine.ts +1 -0
- package/apps/dashboard/lib/session-state-utils.ts +0 -164
- package/apps/dashboard/lib/session-stream-manager.ts +308 -134
- package/apps/dashboard/lib/shadows.ts +7 -0
- package/apps/dashboard/lib/stream-manager-registry.ts +46 -6
- package/apps/dashboard/lib/tauri-bridge.ts +102 -0
- package/apps/dashboard/lib/tauri.ts +106 -0
- package/apps/dashboard/lib/utils.ts +6 -0
- package/apps/dashboard/next-env.d.ts +1 -1
- package/apps/dashboard/package.json +21 -32
- package/apps/dashboard/public/bug-icon.png +0 -0
- package/apps/dashboard/public/buoy-icon.png +0 -0
- package/apps/dashboard/public/fonts/Satoshi-Variable.woff2 +0 -0
- package/apps/dashboard/public/fonts/Satoshi-VariableItalic.woff2 +0 -0
- package/apps/dashboard/public/in-flight-seagull.png +0 -0
- package/apps/dashboard/public/jetty-icon-loading-alt.svg +11 -0
- package/apps/dashboard/public/jetty-icon-loading.svg +11 -0
- package/apps/dashboard/public/jettypod_logo.png +0 -0
- package/apps/dashboard/public/pier-icon.png +0 -0
- package/apps/dashboard/public/star-icon.png +0 -0
- package/apps/dashboard/public/wrench-icon.png +0 -0
- package/apps/dashboard/scripts/tauri-build.js +228 -0
- package/apps/dashboard/scripts/upload-tauri-to-r2.js +125 -0
- package/apps/dashboard/scripts/ws-server.js +191 -0
- package/apps/dashboard/src/main.tsx +12 -0
- package/apps/dashboard/src/router.tsx +107 -0
- package/apps/dashboard/src/vite-env.d.ts +1 -0
- package/apps/dashboard/tsconfig.json +7 -12
- package/apps/dashboard/tsconfig.tsbuildinfo +1 -1
- package/apps/dashboard/vite.config.ts +33 -0
- package/apps/update-server/src/index.ts +228 -80
- package/claude-hooks/global-guardrails.js +14 -13
- package/crates/jettypod-cli/Cargo.toml +19 -0
- package/crates/jettypod-cli/src/commands.rs +1249 -0
- package/crates/jettypod-cli/src/main.rs +595 -0
- package/crates/jettypod-core/Cargo.toml +26 -0
- package/crates/jettypod-core/build.rs +98 -0
- package/crates/jettypod-core/migrations/V1__baseline.sql +197 -0
- package/crates/jettypod-core/migrations/V2__work_items_indexes.sql +6 -0
- package/crates/jettypod-core/migrations/V3__qa_steps.sql +2 -0
- package/crates/jettypod-core/src/auth.rs +294 -0
- package/crates/jettypod-core/src/config.rs +397 -0
- package/crates/jettypod-core/src/db/mod.rs +507 -0
- package/crates/jettypod-core/src/db/recovery.rs +114 -0
- package/crates/jettypod-core/src/db/startup.rs +101 -0
- package/crates/jettypod-core/src/db/validate.rs +149 -0
- package/crates/jettypod-core/src/error.rs +76 -0
- package/crates/jettypod-core/src/git.rs +458 -0
- package/crates/jettypod-core/src/lib.rs +20 -0
- package/crates/jettypod-core/src/sessions.rs +625 -0
- package/crates/jettypod-core/src/skills.rs +556 -0
- package/crates/jettypod-core/src/work.rs +1086 -0
- package/crates/jettypod-core/src/worktree.rs +628 -0
- package/crates/jettypod-core/src/ws.rs +767 -0
- package/cucumber-test.cjs +6 -0
- package/cucumber.js +9 -3
- package/docs/COMMAND_REFERENCE.md +34 -0
- package/hooks/post-checkout +32 -75
- package/hooks/post-merge +111 -10
- package/jest.setup.js +1 -0
- package/jettypod.js +145 -116
- package/lib/bdd-preflight.js +96 -0
- package/lib/chore-taxonomy.js +33 -10
- package/lib/database.js +36 -16
- package/lib/db-watcher.js +1 -1
- package/lib/git-hooks/pre-commit +1 -1
- package/lib/jettypod-backup.js +27 -4
- package/lib/merge-lock.js +111 -253
- package/lib/migrations/027-plan-at-creation-column.js +3 -1
- package/lib/migrations/029-remove-autoincrement.js +307 -0
- package/lib/migrations/029-rename-corrupted-to-cleaned.js +149 -0
- package/lib/migrations/030-rejection-round-columns.js +54 -0
- package/lib/migrations/031-session-isolation-index.js +17 -0
- package/lib/migrations/index.js +47 -4
- package/lib/schema.js +10 -5
- package/lib/seed-onboarding.js +1 -1
- package/lib/update-command/index.js +9 -175
- package/lib/work-commands/index.js +144 -19
- package/lib/work-tracking/index.js +148 -27
- package/lib/worktree-diagnostics.js +16 -16
- package/lib/worktree-facade.js +1 -1
- package/lib/worktree-manager.js +8 -8
- package/lib/worktree-reconciler.js +5 -5
- package/package.json +9 -2
- package/scripts/ndjson-to-cucumber-json.js +152 -0
- package/scripts/postinstall.js +25 -0
- package/skills-templates/bug-mode/SKILL.md +79 -20
- package/skills-templates/bug-planning/SKILL.md +25 -29
- package/skills-templates/chore-mode/SKILL.md +171 -69
- package/skills-templates/chore-mode/verification.js +51 -10
- package/skills-templates/chore-planning/SKILL.md +47 -18
- package/skills-templates/design-system-selection/SKILL.md +273 -0
- package/skills-templates/epic-planning/SKILL.md +82 -48
- package/skills-templates/external-transition/SKILL.md +47 -47
- package/skills-templates/feature-planning/SKILL.md +173 -74
- package/skills-templates/production-mode/SKILL.md +69 -49
- package/skills-templates/request-routing/SKILL.md +4 -4
- package/skills-templates/simple-improvement/SKILL.md +74 -29
- package/skills-templates/speed-mode/SKILL.md +217 -141
- package/skills-templates/stable-mode/SKILL.md +148 -89
- package/apps/dashboard/README.md +0 -36
- package/apps/dashboard/app/api/claude/[workItemId]/message/route.ts +0 -386
- package/apps/dashboard/app/api/claude/[workItemId]/pin/route.ts +0 -24
- package/apps/dashboard/app/api/claude/[workItemId]/route.ts +0 -167
- package/apps/dashboard/app/api/claude/sessions/[sessionId]/content/route.ts +0 -52
- package/apps/dashboard/app/api/claude/sessions/[sessionId]/message/route.ts +0 -378
- package/apps/dashboard/app/api/claude/sessions/[sessionId]/pin/route.ts +0 -24
- package/apps/dashboard/app/api/claude/sessions/cleanup/route.ts +0 -34
- package/apps/dashboard/app/api/claude/sessions/route.ts +0 -184
- package/apps/dashboard/app/api/decisions/[id]/route.ts +0 -25
- package/apps/dashboard/app/api/internal/set-project/route.ts +0 -17
- package/apps/dashboard/app/api/kanban/route.ts +0 -15
- package/apps/dashboard/app/api/settings/env-vars/route.ts +0 -125
- package/apps/dashboard/app/api/settings/general/route.ts +0 -21
- package/apps/dashboard/app/api/tests/route.ts +0 -9
- package/apps/dashboard/app/api/tests/run/route.ts +0 -82
- package/apps/dashboard/app/api/tests/run/stream/route.ts +0 -71
- package/apps/dashboard/app/api/tests/undefined/route.ts +0 -9
- package/apps/dashboard/app/api/usage/route.ts +0 -17
- package/apps/dashboard/app/api/work/[id]/description/route.ts +0 -21
- package/apps/dashboard/app/api/work/[id]/epic/route.ts +0 -21
- package/apps/dashboard/app/api/work/[id]/order/route.ts +0 -21
- package/apps/dashboard/app/api/work/[id]/status/route.ts +0 -21
- package/apps/dashboard/app/api/work/[id]/title/route.ts +0 -21
- package/apps/dashboard/app/layout.tsx +0 -43
- package/apps/dashboard/components/UpgradeBanner.tsx +0 -29
- package/apps/dashboard/electron/ipc-handlers.js +0 -1028
- package/apps/dashboard/electron/main.js +0 -2124
- package/apps/dashboard/electron/preload.js +0 -123
- package/apps/dashboard/electron/session-manager.js +0 -141
- package/apps/dashboard/electron-builder.config.js +0 -357
- package/apps/dashboard/hooks/useClaudeSessions.ts +0 -299
- package/apps/dashboard/lib/claude-process-manager.ts +0 -492
- package/apps/dashboard/lib/db-bridge.ts +0 -282
- package/apps/dashboard/lib/prototypes.ts +0 -202
- package/apps/dashboard/lib/test-results-db.ts +0 -307
- package/apps/dashboard/lib/tests.ts +0 -282
- package/apps/dashboard/next.config.js +0 -50
- package/apps/dashboard/postcss.config.mjs +0 -7
- package/apps/dashboard/public/file.svg +0 -1
- package/apps/dashboard/public/globe.svg +0 -1
- package/apps/dashboard/public/next.svg +0 -1
- package/apps/dashboard/public/vercel.svg +0 -1
- package/apps/dashboard/public/window.svg +0 -1
- package/apps/dashboard/scripts/download-node.js +0 -104
- package/apps/dashboard/scripts/upload-to-r2.js +0 -89
- 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
|
-
}
|