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,125 +0,0 @@
1
- import { NextRequest, NextResponse } from 'next/server';
2
- import {
3
- getEnvVars,
4
- setEnvVar,
5
- deleteEnvVar,
6
- discoverEnvFiles,
7
- getSelectedEnvFile,
8
- setSelectedEnvFile,
9
- createEnvFile,
10
- validateEnvVarName,
11
- validateEnvVarValue,
12
- checkDuplicateEnvVar,
13
- } from '@/lib/db';
14
-
15
- // GET /api/settings/env-vars
16
- // Query params: ?file=.env.local (optional)
17
- // Also accepts ?action=discover to list available .env files
18
- // Also accepts ?action=selected to get the currently selected file
19
- export async function GET(request: NextRequest) {
20
- try {
21
- const { searchParams } = new URL(request.url);
22
- const action = searchParams.get('action');
23
-
24
- if (action === 'discover') {
25
- const files = discoverEnvFiles();
26
- return NextResponse.json({ files });
27
- }
28
-
29
- if (action === 'selected') {
30
- const selected = getSelectedEnvFile();
31
- return NextResponse.json({ selected });
32
- }
33
-
34
- const file = searchParams.get('file') || undefined;
35
- const envVars = getEnvVars(file);
36
- return NextResponse.json(envVars);
37
- } catch (error) {
38
- return NextResponse.json(
39
- { error: 'Failed to read environment variables' },
40
- { status: 500 }
41
- );
42
- }
43
- }
44
-
45
- // POST /api/settings/env-vars
46
- // Body: { name, value, file? } to add/update a variable
47
- // Body: { action: 'select', file } to set selected file
48
- // Body: { action: 'create', file? } to create a new .env file
49
- export async function POST(request: NextRequest) {
50
- try {
51
- const body = await request.json();
52
-
53
- if (body.action === 'select') {
54
- setSelectedEnvFile(body.file);
55
- return NextResponse.json({ success: true });
56
- }
57
-
58
- if (body.action === 'create') {
59
- const filename = body.file || '.env';
60
- createEnvFile(filename);
61
- return NextResponse.json({ success: true, file: filename });
62
- }
63
-
64
- const { name, value, file } = body;
65
-
66
- // Validate name
67
- const nameCheck = validateEnvVarName(name);
68
- if (!nameCheck.valid) {
69
- return NextResponse.json(
70
- { error: nameCheck.error },
71
- { status: 400 }
72
- );
73
- }
74
-
75
- // Validate value
76
- const valueCheck = validateEnvVarValue(value);
77
- if (!valueCheck.valid) {
78
- return NextResponse.json(
79
- { error: valueCheck.error },
80
- { status: 400 }
81
- );
82
- }
83
-
84
- // Check for duplicates when adding (not updating)
85
- if (body.action === 'add' && checkDuplicateEnvVar(name, file)) {
86
- return NextResponse.json(
87
- { error: 'Variable already exists' },
88
- { status: 409 }
89
- );
90
- }
91
-
92
- setEnvVar(name, value, file);
93
- return NextResponse.json({ success: true });
94
- } catch (error) {
95
- return NextResponse.json(
96
- { error: 'Failed to save environment variable' },
97
- { status: 500 }
98
- );
99
- }
100
- }
101
-
102
- // DELETE /api/settings/env-vars
103
- // Body: { name, file? }
104
- export async function DELETE(request: NextRequest) {
105
- try {
106
- const body = await request.json();
107
- const { name, file } = body;
108
-
109
- const nameCheck = validateEnvVarName(name);
110
- if (!nameCheck.valid) {
111
- return NextResponse.json(
112
- { error: nameCheck.error },
113
- { status: 400 }
114
- );
115
- }
116
-
117
- deleteEnvVar(name, file);
118
- return NextResponse.json({ success: true });
119
- } catch (error) {
120
- return NextResponse.json(
121
- { error: 'Failed to delete environment variable' },
122
- { status: 500 }
123
- );
124
- }
125
- }
@@ -1,21 +0,0 @@
1
- import { NextRequest, NextResponse } from 'next/server';
2
- import { getMainBranch, setMainBranch } from '@/lib/db';
3
-
4
- // GET /api/settings/general
5
- export async function GET() {
6
- const mainBranch = getMainBranch();
7
- return NextResponse.json({ mainBranch });
8
- }
9
-
10
- // POST /api/settings/general
11
- // Body: { mainBranch: string | null }
12
- export async function POST(request: NextRequest) {
13
- const body = await request.json();
14
-
15
- if ('mainBranch' in body) {
16
- setMainBranch(body.mainBranch);
17
- }
18
-
19
- const mainBranch = getMainBranch();
20
- return NextResponse.json({ mainBranch });
21
- }
@@ -1,9 +0,0 @@
1
- import { NextResponse } from 'next/server';
2
- import { getTestDashboardData } from '@/lib/tests';
3
-
4
- export const dynamic = 'force-dynamic';
5
-
6
- export async function GET() {
7
- const data = getTestDashboardData();
8
- return NextResponse.json(data);
9
- }
@@ -1,82 +0,0 @@
1
- import { NextResponse } from 'next/server';
2
- import { ingestCucumberResults } from '@/lib/test-results-db';
3
-
4
- export const dynamic = 'force-dynamic';
5
-
6
- // Shared test execution engine (CommonJS)
7
- // eslint-disable-next-line @typescript-eslint/no-var-requires
8
- const { runScenario } = require('../../../../../../lib/test-runner');
9
-
10
- export async function POST(request: Request) {
11
- let body: { featureFile?: unknown; scenarioTitle?: unknown };
12
- try {
13
- body = await request.json();
14
- } catch {
15
- return NextResponse.json(
16
- { error: 'Invalid JSON in request body' },
17
- { status: 400 }
18
- );
19
- }
20
-
21
- const { featureFile, scenarioTitle } = body;
22
-
23
- if (!featureFile || !scenarioTitle) {
24
- return NextResponse.json(
25
- { error: 'featureFile and scenarioTitle are required' },
26
- { status: 400 }
27
- );
28
- }
29
-
30
- if (typeof featureFile !== 'string' || typeof scenarioTitle !== 'string') {
31
- return NextResponse.json(
32
- { error: 'featureFile and scenarioTitle must be strings' },
33
- { status: 400 }
34
- );
35
- }
36
-
37
- if (!featureFile.endsWith('.feature')) {
38
- return NextResponse.json(
39
- { error: 'featureFile must be a .feature file' },
40
- { status: 400 }
41
- );
42
- }
43
-
44
- if (featureFile.includes('..')) {
45
- return NextResponse.json(
46
- { error: 'featureFile must not contain path traversal' },
47
- { status: 400 }
48
- );
49
- }
50
-
51
- if (scenarioTitle.length > 500) {
52
- return NextResponse.json(
53
- { error: 'scenarioTitle must be 500 characters or less' },
54
- { status: 400 }
55
- );
56
- }
57
-
58
- try {
59
- const result = await runScenario(featureFile, scenarioTitle, {
60
- timeout: 60000,
61
- });
62
-
63
- // Ingest results into SQLite so dashboard updates immediately
64
- try {
65
- ingestCucumberResults(result.resultsPath);
66
- } catch {
67
- // Non-fatal: results file may not exist if cucumber crashed
68
- }
69
-
70
- return NextResponse.json({
71
- success: result.success,
72
- exitCode: result.exitCode,
73
- stdout: result.stdout,
74
- stderr: result.stderr,
75
- });
76
- } catch (err) {
77
- return NextResponse.json(
78
- { error: 'Failed to execute test', details: String(err) },
79
- { status: 500 }
80
- );
81
- }
82
- }
@@ -1,71 +0,0 @@
1
- import { NextRequest } from 'next/server';
2
- import { ingestCucumberResults } from '@/lib/test-results-db';
3
-
4
- export const dynamic = 'force-dynamic';
5
-
6
- // Shared test execution engine (CommonJS)
7
- // eslint-disable-next-line @typescript-eslint/no-var-requires
8
- const { runScenario, runFeature } = require('../../../../../../../lib/test-runner');
9
-
10
- export async function GET(request: NextRequest) {
11
- const { searchParams } = new URL(request.url);
12
- const featureFile = searchParams.get('featureFile');
13
- const scenarioTitle = searchParams.get('scenarioTitle');
14
-
15
- if (!featureFile) {
16
- return new Response('featureFile is required', { status: 400 });
17
- }
18
-
19
- if (!featureFile.endsWith('.feature') || featureFile.includes('..')) {
20
- return new Response('Invalid featureFile', { status: 400 });
21
- }
22
-
23
- const encoder = new TextEncoder();
24
- const stream = new ReadableStream({
25
- start(controller) {
26
- function send(event: string, data: Record<string, unknown>) {
27
- controller.enqueue(encoder.encode(`event: ${event}\ndata: ${JSON.stringify(data)}\n\n`));
28
- }
29
-
30
- // Hold back test_complete events from the runner — we need to ingest
31
- // results into SQLite BEFORE the frontend refreshes, otherwise it reads stale data.
32
- let heldTestComplete: Record<string, unknown> | null = null;
33
-
34
- const onEvent = (event: { type: string; [key: string]: unknown }) => {
35
- const { type, ...data } = event;
36
- if (type === 'test_complete') {
37
- heldTestComplete = data;
38
- return;
39
- }
40
- send(type, data);
41
- };
42
-
43
- // If scenarioTitle provided, run single scenario; otherwise run entire feature
44
- const run = scenarioTitle
45
- ? runScenario(featureFile, scenarioTitle, { onEvent, signal: request.signal })
46
- : runFeature(featureFile, { onEvent, signal: request.signal });
47
-
48
- run.then((result: { resultsPath: string }) => {
49
- // Ingest results into SQLite BEFORE sending test_complete
50
- try {
51
- ingestCucumberResults(result.resultsPath);
52
- } catch {
53
- // Non-fatal
54
- }
55
- send('test_complete', heldTestComplete || { status: 'fail' });
56
- controller.close();
57
- }).catch(() => {
58
- send('test_complete', { status: 'fail' });
59
- controller.close();
60
- });
61
- },
62
- });
63
-
64
- return new Response(stream, {
65
- headers: {
66
- 'Content-Type': 'text/event-stream',
67
- 'Cache-Control': 'no-cache',
68
- 'Connection': 'keep-alive',
69
- },
70
- });
71
- }
@@ -1,9 +0,0 @@
1
- import { NextResponse } from 'next/server';
2
- import { getUndefinedSteps } from '@/lib/tests';
3
-
4
- export const dynamic = 'force-dynamic';
5
-
6
- export async function GET() {
7
- const undefinedSteps = getUndefinedSteps();
8
- return NextResponse.json(undefinedSteps);
9
- }
@@ -1,17 +0,0 @@
1
- import { NextResponse } from 'next/server';
2
- import { getWeeklyUsage } from '@/lib/db';
3
-
4
- export const dynamic = 'force-dynamic';
5
-
6
- const SAFE_DEFAULT = { used: 0, limit: 20, remaining: 20, allowed: true };
7
-
8
- export async function GET() {
9
- try {
10
- const usage = getWeeklyUsage();
11
- console.log('[usage] /api/usage responding with', usage);
12
- return NextResponse.json(usage);
13
- } catch (err) {
14
- console.error('[usage] /api/usage ERROR — returning safe default', err);
15
- return NextResponse.json(SAFE_DEFAULT);
16
- }
17
- }
@@ -1,21 +0,0 @@
1
- import { NextRequest, NextResponse } from 'next/server';
2
- import { updateWorkItemDescription } from '@/lib/db';
3
-
4
- export async function PATCH(
5
- request: NextRequest,
6
- { params }: { params: Promise<{ id: string }> }
7
- ) {
8
- const { id } = await params;
9
- const workItemId = parseInt(id, 10);
10
-
11
- const body = await request.json();
12
- const { description } = body;
13
-
14
- const updated = updateWorkItemDescription(workItemId, description);
15
-
16
- if (updated) {
17
- return NextResponse.json({ success: true, id: workItemId, description });
18
- } else {
19
- return NextResponse.json({ success: false, error: 'Work item not found' }, { status: 404 });
20
- }
21
- }
@@ -1,21 +0,0 @@
1
- import { NextRequest, NextResponse } from 'next/server';
2
- import { updateWorkItemEpic } from '@/lib/db';
3
-
4
- export async function PATCH(
5
- request: NextRequest,
6
- { params }: { params: Promise<{ id: string }> }
7
- ) {
8
- const { id } = await params;
9
- const workItemId = parseInt(id, 10);
10
-
11
- const body = await request.json();
12
- const { epic_id } = body;
13
-
14
- const updated = updateWorkItemEpic(workItemId, epic_id);
15
-
16
- if (updated) {
17
- return NextResponse.json({ success: true, id: workItemId, epic_id });
18
- } else {
19
- return NextResponse.json({ success: false, error: 'Work item not found or cannot be assigned to epic' }, { status: 404 });
20
- }
21
- }
@@ -1,21 +0,0 @@
1
- import { NextRequest, NextResponse } from 'next/server';
2
- import { updateWorkItemOrder } from '@/lib/db';
3
-
4
- export async function PATCH(
5
- request: NextRequest,
6
- { params }: { params: Promise<{ id: string }> }
7
- ) {
8
- const { id } = await params;
9
- const workItemId = parseInt(id, 10);
10
-
11
- const body = await request.json();
12
- const { display_order } = body;
13
-
14
- const updated = updateWorkItemOrder(workItemId, display_order);
15
-
16
- if (updated) {
17
- return NextResponse.json({ success: true, id: workItemId, display_order });
18
- } else {
19
- return NextResponse.json({ success: false, error: 'Work item not found' }, { status: 404 });
20
- }
21
- }
@@ -1,21 +0,0 @@
1
- import { NextRequest, NextResponse } from 'next/server';
2
- import { updateWorkItemStatus } from '@/lib/db';
3
-
4
- export async function PATCH(
5
- request: NextRequest,
6
- { params }: { params: Promise<{ id: string }> }
7
- ) {
8
- const { id } = await params;
9
- const workItemId = parseInt(id, 10);
10
-
11
- const body = await request.json();
12
- const { status, rejectionReason } = body;
13
-
14
- const updated = updateWorkItemStatus(workItemId, status, rejectionReason);
15
-
16
- if (updated) {
17
- return NextResponse.json({ success: true, id: workItemId, status });
18
- } else {
19
- return NextResponse.json({ success: false, error: 'Work item not found' }, { status: 404 });
20
- }
21
- }
@@ -1,21 +0,0 @@
1
- import { NextRequest, NextResponse } from 'next/server';
2
- import { updateWorkItemTitle } from '@/lib/db';
3
-
4
- export async function PATCH(
5
- request: NextRequest,
6
- { params }: { params: Promise<{ id: string }> }
7
- ) {
8
- const { id } = await params;
9
- const workItemId = parseInt(id, 10);
10
-
11
- const body = await request.json();
12
- const { title } = body;
13
-
14
- const updated = updateWorkItemTitle(workItemId, title);
15
-
16
- if (updated) {
17
- return NextResponse.json({ success: true, id: workItemId, title });
18
- } else {
19
- return NextResponse.json({ success: false, error: 'Work item not found' }, { status: 404 });
20
- }
21
- }
@@ -1,43 +0,0 @@
1
- import type { Metadata } from "next";
2
- import { Geist, Geist_Mono } from "next/font/google";
3
- import "./globals.css";
4
- import { getProjectName } from "@/lib/db";
5
- import { AppShell } from "@/components/AppShell";
6
-
7
- const geistSans = Geist({
8
- variable: "--font-geist-sans",
9
- subsets: ["latin"],
10
- });
11
-
12
- const geistMono = Geist_Mono({
13
- variable: "--font-geist-mono",
14
- subsets: ["latin"],
15
- });
16
-
17
- export function generateMetadata(): Metadata {
18
- const projectName = getProjectName();
19
- return {
20
- title: `JettyPod | ${projectName}`,
21
- description: "JettyPod dashboard",
22
- };
23
- }
24
-
25
- export default function RootLayout({
26
- children,
27
- }: Readonly<{
28
- children: React.ReactNode;
29
- }>) {
30
- const projectName = getProjectName();
31
-
32
- return (
33
- <html lang="en" suppressHydrationWarning>
34
- <body
35
- className={`${geistSans.variable} ${geistMono.variable} antialiased`}
36
- >
37
- <AppShell projectName={projectName}>
38
- {children}
39
- </AppShell>
40
- </body>
41
- </html>
42
- );
43
- }
@@ -1,29 +0,0 @@
1
- 'use client';
2
-
3
- import { useUsage } from '../contexts/UsageContext';
4
-
5
- export function UpgradeBanner() {
6
- const { allowed, used, limit, plan, loading } = useUsage();
7
-
8
- if (loading || allowed || plan !== 'free') return null;
9
-
10
- return (
11
- <div
12
- className="bg-amber-50 dark:bg-amber-900/20 border border-amber-200 dark:border-amber-800 text-amber-800 dark:text-amber-200 px-4 py-3 rounded-lg flex items-center justify-between flex-shrink-0"
13
- data-testid="upgrade-banner"
14
- >
15
- <div className="flex items-center gap-2">
16
- <span className="text-amber-600 dark:text-amber-400 text-lg">&#9888;</span>
17
- <span className="text-sm font-medium">
18
- Weekly limit reached ({used}/{limit} work items). Claude features are disabled until your usage resets.
19
- </span>
20
- </div>
21
- <a
22
- href="/subscribe"
23
- className="px-3 py-1.5 bg-amber-600 hover:bg-amber-700 text-white text-sm font-medium rounded-lg transition-colors whitespace-nowrap"
24
- >
25
- Upgrade
26
- </a>
27
- </div>
28
- );
29
- }