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,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,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">⚠</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
|
-
}
|