jettypod 4.4.106 → 4.4.107

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.
Binary file
@@ -1,5 +1,5 @@
1
1
  import { NextResponse } from 'next/server';
2
- import { listSessions, createSession, linkSession, getSessionByWorkItem } from '@/lib/db';
2
+ import { listSessions, createSession, linkSession, getSessionByWorkItem, closeSession } from '@/lib/db';
3
3
 
4
4
  export const dynamic = 'force-dynamic';
5
5
 
@@ -59,3 +59,25 @@ export async function PATCH(request: Request) {
59
59
  return NextResponse.json({ error: 'Failed to link session' }, { status: 500 });
60
60
  }
61
61
  }
62
+
63
+ // DELETE /api/claude/sessions - Close a session
64
+ export async function DELETE(request: Request) {
65
+ try {
66
+ const { searchParams } = new URL(request.url);
67
+ const sessionId = searchParams.get('sessionId');
68
+
69
+ if (!sessionId) {
70
+ return NextResponse.json({ error: 'sessionId required' }, { status: 400 });
71
+ }
72
+
73
+ const success = closeSession(parseInt(sessionId, 10));
74
+ if (!success) {
75
+ return NextResponse.json({ error: 'Session not found' }, { status: 404 });
76
+ }
77
+
78
+ return NextResponse.json({ success: true });
79
+ } catch (error) {
80
+ console.error('Failed to close session:', error);
81
+ return NextResponse.json({ error: 'Failed to close session' }, { status: 500 });
82
+ }
83
+ }
@@ -40,6 +40,7 @@ interface ClaudePanelProps {
40
40
  standaloneSessions?: SessionItem[];
41
41
  onSelectSession?: (sessionId: string) => void;
42
42
  onNewSession?: () => void;
43
+ onCloseSession?: (sessionId: string) => void;
43
44
  showSessionList?: boolean;
44
45
  }
45
46
 
@@ -62,6 +63,7 @@ export function ClaudePanel({
62
63
  standaloneSessions = [],
63
64
  onSelectSession,
64
65
  onNewSession,
66
+ onCloseSession,
65
67
  showSessionList: showSessionListProp,
66
68
  }: ClaudePanelProps) {
67
69
  // Show session list by default when no active session, or when explicitly requested
@@ -218,6 +220,7 @@ export function ClaudePanel({
218
220
  sessions={standaloneSessions}
219
221
  onSelectSession={onSelectSession || (() => {})}
220
222
  onNewSession={onNewSession || (() => {})}
223
+ onCloseSession={onCloseSession}
221
224
  />
222
225
  ) : (
223
226
  <>
@@ -533,6 +533,32 @@ function RealTimeKanbanContent({ initialData, initialDecisions }: RealTimeKanban
533
533
  setShowSessionList(false);
534
534
  }, [sessions, standaloneSessions]);
535
535
 
536
+ const handleCloseSession = useCallback(async (sessionId: string) => {
537
+ // Call API to mark as completed in DB
538
+ try {
539
+ await fetch(`/api/claude/sessions?sessionId=${sessionId}`, {
540
+ method: 'DELETE',
541
+ });
542
+ } catch (error) {
543
+ console.error('Failed to close session:', error);
544
+ }
545
+
546
+ // Remove from standalone sessions list
547
+ setStandaloneSessions(prev => prev.filter(s => s.id !== sessionId));
548
+
549
+ // Remove from in-memory sessions
550
+ setSessions(prev => {
551
+ const updated = new Map(prev);
552
+ updated.delete(sessionId);
553
+ return updated;
554
+ });
555
+
556
+ // If this was the active session, clear it
557
+ if (activeSessionId === sessionId) {
558
+ setActiveSessionId(null);
559
+ }
560
+ }, [activeSessionId]);
561
+
536
562
  const handleOpenSessionList = useCallback(() => {
537
563
  setShowSessionList(true);
538
564
  setClaudePanelOpen(true);
@@ -639,6 +665,7 @@ function RealTimeKanbanContent({ initialData, initialDecisions }: RealTimeKanban
639
665
  standaloneSessions={standaloneSessions}
640
666
  onSelectSession={handleSelectStandaloneSession}
641
667
  onNewSession={handleNewSession}
668
+ onCloseSession={handleCloseSession}
642
669
  showSessionList={showSessionList}
643
670
  />
644
671
  </div>
@@ -14,12 +14,14 @@ interface SessionListProps {
14
14
  sessions: SessionItem[];
15
15
  onSelectSession: (sessionId: string) => void;
16
16
  onNewSession: () => void;
17
+ onCloseSession?: (sessionId: string) => void;
17
18
  }
18
19
 
19
20
  export function SessionList({
20
21
  sessions,
21
22
  onSelectSession,
22
23
  onNewSession,
24
+ onCloseSession,
23
25
  }: SessionListProps) {
24
26
  return (
25
27
  <div className="flex-1 flex flex-col" data-testid="session-list">
@@ -46,31 +48,48 @@ export function SessionList({
46
48
  ) : (
47
49
  <div className="divide-y divide-zinc-800">
48
50
  {sessions.map((session) => (
49
- <motion.button
51
+ <div
50
52
  key={session.id}
51
- onClick={() => onSelectSession(session.id)}
52
- className="w-full px-4 py-3 text-left hover:bg-zinc-800/50 transition-colors"
53
- whileHover={{ x: 4 }}
53
+ className="flex items-center hover:bg-zinc-800/50 transition-colors"
54
54
  data-testid={`session-item-${session.id}`}
55
55
  >
56
- <div className="flex items-center gap-2">
57
- <SessionIcon hasFeature={!!session.featureId} />
58
- <div className="flex-1 min-w-0">
59
- <p className="text-sm font-medium text-white truncate">
60
- {session.featureId ? session.featureTitle : session.title}
61
- </p>
62
- {session.featureId && (
63
- <span className="inline-flex items-center px-1.5 py-0.5 mt-1 text-xs font-medium bg-blue-900/50 text-blue-300 rounded">
64
- #{session.featureId}
65
- </span>
66
- )}
67
- {!session.featureId && (
68
- <p className="text-xs text-zinc-500 mt-0.5">Unlinked session</p>
69
- )}
56
+ <motion.button
57
+ onClick={() => onSelectSession(session.id)}
58
+ className="flex-1 px-4 py-3 text-left"
59
+ whileHover={{ x: 4 }}
60
+ >
61
+ <div className="flex items-center gap-2">
62
+ <SessionIcon hasFeature={!!session.featureId} />
63
+ <div className="flex-1 min-w-0">
64
+ <p className="text-sm font-medium text-white truncate">
65
+ {session.featureId ? session.featureTitle : session.title}
66
+ </p>
67
+ {session.featureId && (
68
+ <span className="inline-flex items-center px-1.5 py-0.5 mt-1 text-xs font-medium bg-blue-900/50 text-blue-300 rounded">
69
+ #{session.featureId}
70
+ </span>
71
+ )}
72
+ {!session.featureId && (
73
+ <p className="text-xs text-zinc-500 mt-0.5">Unlinked session</p>
74
+ )}
75
+ </div>
76
+ <ChevronIcon />
70
77
  </div>
71
- <ChevronIcon />
72
- </div>
73
- </motion.button>
78
+ </motion.button>
79
+ {onCloseSession && (
80
+ <button
81
+ onClick={(e) => {
82
+ e.stopPropagation();
83
+ onCloseSession(session.id);
84
+ }}
85
+ className="p-2 mr-2 rounded hover:bg-zinc-700 text-zinc-500 hover:text-zinc-300 transition-colors"
86
+ aria-label="Close session"
87
+ data-testid={`close-session-${session.id}`}
88
+ >
89
+ <CloseIcon />
90
+ </button>
91
+ )}
92
+ </div>
74
93
  ))}
75
94
  </div>
76
95
  )}
@@ -118,3 +137,21 @@ function ChevronIcon() {
118
137
  </svg>
119
138
  );
120
139
  }
140
+
141
+ function CloseIcon() {
142
+ return (
143
+ <svg
144
+ className="w-4 h-4"
145
+ fill="none"
146
+ stroke="currentColor"
147
+ viewBox="0 0 24 24"
148
+ >
149
+ <path
150
+ strokeLinecap="round"
151
+ strokeLinejoin="round"
152
+ strokeWidth={2}
153
+ d="M6 18L18 6M6 6l12 12"
154
+ />
155
+ </svg>
156
+ );
157
+ }
@@ -23,6 +23,7 @@ interface UseClaudeSessionsReturn {
23
23
  switchSession: (workItemId: string) => void;
24
24
  stopSession: (workItemId: string) => void;
25
25
  retrySession: (workItemId: string) => void;
26
+ closeSession: (workItemId: string, dbSessionId?: number) => Promise<void>;
26
27
  closeAllSessions: () => void;
27
28
  isSessionRunning: (workItemId: string) => boolean;
28
29
  }
@@ -234,6 +235,38 @@ export function useClaudeSessions(): UseClaudeSessionsReturn {
234
235
  }
235
236
  }, [sessions, startSession]);
236
237
 
238
+ const closeSession = useCallback(async (workItemId: string, dbSessionId?: number) => {
239
+ const session = sessions.get(workItemId);
240
+
241
+ // Abort if running
242
+ if (session?.abortController) {
243
+ session.abortController.abort();
244
+ }
245
+
246
+ // Call API to mark as completed in DB (if we have a DB session ID)
247
+ if (dbSessionId) {
248
+ try {
249
+ await fetch(`/api/claude/sessions?sessionId=${dbSessionId}`, {
250
+ method: 'DELETE',
251
+ });
252
+ } catch (error) {
253
+ console.error('Failed to close session in DB:', error);
254
+ }
255
+ }
256
+
257
+ // Remove from local state
258
+ setSessions((prev) => {
259
+ const newMap = new Map(prev);
260
+ newMap.delete(workItemId);
261
+ return newMap;
262
+ });
263
+
264
+ // If this was the active session, clear it
265
+ if (activeSessionId === workItemId) {
266
+ setActiveSessionId(null);
267
+ }
268
+ }, [sessions, activeSessionId]);
269
+
237
270
  const closeAllSessions = useCallback(() => {
238
271
  sessions.forEach((session) => {
239
272
  if (session.abortController) {
@@ -259,6 +292,7 @@ export function useClaudeSessions(): UseClaudeSessionsReturn {
259
292
  switchSession,
260
293
  stopSession,
261
294
  retrySession,
295
+ closeSession,
262
296
  closeAllSessions,
263
297
  isSessionRunning,
264
298
  };
@@ -649,3 +649,18 @@ export function getSession(sessionId: number): ClaudeSession | null {
649
649
  db.close();
650
650
  }
651
651
  }
652
+
653
+ // Close a session by ID (mark as completed)
654
+ export function closeSession(sessionId: number): boolean {
655
+ const db = getWriteDb();
656
+ try {
657
+ const result = db.prepare(`
658
+ UPDATE claude_sessions
659
+ SET status = 'completed', completed_at = datetime('now')
660
+ WHERE id = ?
661
+ `).run(sessionId);
662
+ return result.changes > 0;
663
+ } finally {
664
+ db.close();
665
+ }
666
+ }
package/jettypod.js CHANGED
@@ -302,7 +302,9 @@ JettyPod: Structured workflow system with skills that guide complex workflows.
302
302
 
303
303
  ## ⚠️ CRITICAL: All Work Starts with request-routing
304
304
 
305
- **WHY:** request-routing creates a safe workspace (worktree) where your changes can actually be committed. Without it, you'll edit files on main, then discover the pre-commit hook blocks you—leaving uncommitted changes that can't be saved.
305
+ **WHY:** The workflow initiated by request-routing eventually creates a safe workspace (worktree) where your changes can actually be committed. Without starting this workflow, you'll edit files on main, then discover the pre-commit hook blocks you—leaving uncommitted changes that can't be saved.
306
+
307
+ **WHEN WORKTREE IS CREATED:** The worktree is created when \`jettypod work start\` runs—this happens in mode skills (speed-mode, stable-mode, chore-mode, bug-mode) or simple-improvement, NOT immediately in request-routing. Planning skills (epic-planning, feature-planning, chore-planning, bug-planning) are READ-ONLY investigation phases—do not edit files during planning.
306
308
 
307
309
  **FIRST RESPONSE RULE:** If user describes ANY code change, your FIRST action must be invoking request-routing. Not after reading files. Not after understanding the problem. FIRST.
308
310
 
@@ -313,10 +315,10 @@ JettyPod: Structured workflow system with skills that guide complex workflows.
313
315
  - "refactor", "migrate", "upgrade" (technical work)
314
316
  - "I noticed...", "I'm thinking..." (when followed by desired change)
315
317
 
316
- **⚠️ ANTI-PATTERN: Do NOT edit files before invoking request-routing.**
318
+ **⚠️ ANTI-PATTERN: Do NOT edit files before the worktree exists.**
317
319
  Wrong: See problem → edit files → realize you're on main → ask about workflow
318
320
  Wrong: Read files → understand problem → try to fix → get blocked → create work item
319
- Right: User describes work → invoke request-routing → skill creates worktree → then edit
321
+ Right: User describes work → invoke request-routing → complete workflow until \`work start\` creates worktree → then edit
320
322
 
321
323
  ## ⚠️ CRITICAL: Skills are MANDATORY for workflows
322
324
  Skills auto-activate and MUST complete their full workflow:
@@ -48,8 +48,9 @@ function backupDatabase() {
48
48
 
49
49
  fs.copyFileSync(sourcePath, backupPath);
50
50
 
51
- // Stage the backup file
52
- execSync(`git add "${backupPath}"`, {
51
+ // Stage the backup file (force-add because .jettypod-backup is gitignored
52
+ // to prevent worktree symlink corruption, but backups must be committed)
53
+ execSync(`git add -f "${backupPath}"`, {
53
54
  stdio: ['pipe', 'pipe', 'pipe']
54
55
  });
55
56
 
@@ -43,7 +43,12 @@ function checkBranchRestriction() {
43
43
  console.error(' 1. Undo your edits: git checkout .');
44
44
  console.error(' 2. Start the workflow: Invoke request-routing skill');
45
45
  console.error('');
46
- console.error('The skill creates a worktree where commits are allowed.');
46
+ console.error('HOW IT WORKS:');
47
+ console.error(' request-routing → planning skill → mode skill → work start');
48
+ console.error('');
49
+ console.error(' The worktree is created when `jettypod work start` runs.');
50
+ console.error(' Planning skills are READ-ONLY investigation phases.');
51
+ console.error(' Only edit files AFTER the worktree exists.');
47
52
  console.error('');
48
53
  return false;
49
54
  } catch (err) {
@@ -558,6 +558,17 @@ function updateStatus(id, status) {
558
558
 
559
559
  // CRITICAL: Merge to main BEFORE cleanup
560
560
  if (isGitRepo()) {
561
+ // Check if branch exists before attempting merge
562
+ const branchExists = execSync(`git branch --list ${worktree.branch_name}`, {
563
+ cwd: gitRoot,
564
+ encoding: 'utf8',
565
+ stdio: 'pipe'
566
+ }).trim();
567
+
568
+ if (!branchExists) {
569
+ // Branch is gone (likely already merged/deleted) - skip merge, just cleanup stale record
570
+ console.warn(`⚠️ Branch "${worktree.branch_name}" no longer exists - cleaning up stale record`);
571
+ } else {
561
572
  try {
562
573
  // Test merge to detect conflicts (without actually merging)
563
574
  const mergeResult = execSync(`git merge --no-commit --no-ff ${worktree.branch_name}`, {
@@ -624,6 +635,7 @@ function updateStatus(id, status) {
624
635
  console.warn(` Worktree preserved for manual resolution`);
625
636
  throw new Error('Merge failed - worktree preserved');
626
637
  }
638
+ } // end else (branch exists)
627
639
  }
628
640
 
629
641
  // Only cleanup after successful merge
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jettypod",
3
- "version": "4.4.106",
3
+ "version": "4.4.107",
4
4
  "description": "AI-powered development workflow manager with TDD, BDD, and automatic test generation",
5
5
  "main": "jettypod.js",
6
6
  "bin": {
@@ -17,6 +17,25 @@ description: Guide structured bug investigation with symptom capture, hypothesis
17
17
 
18
18
  Guides Claude through systematic bug investigation. Produces a bug work item with clear breadcrumbs for implementation.
19
19
 
20
+ ## ⚠️ READ-ONLY PHASE
21
+
22
+ **This skill is an investigation phase. No worktree exists yet.**
23
+
24
+ 🚫 **FORBIDDEN during this skill:**
25
+ - Writing or editing any code files
26
+ - Creating new files
27
+ - Making implementation changes
28
+ - Adding temporary debugging code
29
+
30
+ ✅ **ALLOWED during this skill:**
31
+ - Reading files to understand the codebase
32
+ - Running `jettypod` commands to create work items
33
+ - Running diagnostic commands (git log, grep, etc.)
34
+ - Asking the user questions
35
+ - Analyzing symptoms and forming hypotheses
36
+
37
+ **The worktree is created in Phase 6** when `jettypod work start` runs before invoking bug-mode.
38
+
20
39
  ## Instructions
21
40
 
22
41
  When this skill is activated, you are investigating a bug to identify root cause and plan the fix.
@@ -7,6 +7,23 @@ description: Guide standalone chore planning with automatic type classification
7
7
 
8
8
  Guides Claude through standalone chore planning including automatic type classification, loading type-specific guidance from the taxonomy, building enriched context, and routing to chore-mode for execution. For chores under **technical epics**, detects this ancestry and passes context to skip mode progression.
9
9
 
10
+ ## ⚠️ READ-ONLY PHASE
11
+
12
+ **This skill is a planning/investigation phase. No worktree exists yet.**
13
+
14
+ 🚫 **FORBIDDEN during this skill:**
15
+ - Writing or editing any code files
16
+ - Creating new files
17
+ - Making implementation changes
18
+
19
+ ✅ **ALLOWED during this skill:**
20
+ - Reading files to understand the codebase
21
+ - Running `jettypod` commands to create work items
22
+ - Asking the user questions
23
+ - Analyzing and planning
24
+
25
+ **The worktree is created later** when chore-mode runs `jettypod work start`.
26
+
10
27
  ## Instructions
11
28
 
12
29
  When this skill is activated, you are helping plan a standalone chore (one without a parent feature). Follow this structured approach:
@@ -7,6 +7,23 @@ description: Guide epic planning with feature brainstorming and optional archite
7
7
 
8
8
  Guides Claude through comprehensive epic planning including feature identification and architectural decisions. For **technical epics**, skips feature brainstorming and creates chores directly.
9
9
 
10
+ ## ⚠️ READ-ONLY PHASE
11
+
12
+ **This skill is a planning/investigation phase. No worktree exists yet.**
13
+
14
+ 🚫 **FORBIDDEN during this skill:**
15
+ - Writing or editing any code files
16
+ - Creating new files
17
+ - Making implementation changes
18
+
19
+ ✅ **ALLOWED during this skill:**
20
+ - Reading files to understand the codebase
21
+ - Running `jettypod` commands to create work items
22
+ - Asking the user questions
23
+ - Analyzing and planning
24
+
25
+ **The worktree is created later** when a mode skill (speed-mode, chore-mode, etc.) runs `jettypod work start`.
26
+
10
27
  ## Instructions
11
28
 
12
29
  When this skill is activated, you are helping plan an epic. Follow this structured approach:
@@ -7,6 +7,27 @@ description: Guide feature planning with UX approach exploration and BDD scenari
7
7
 
8
8
  Guides Claude through feature planning including UX approach exploration, optional prototyping, and BDD scenario generation.
9
9
 
10
+ ## ⚠️ READ-ONLY UNTIL WORKTREE EXISTS
11
+
12
+ **This skill starts as a planning/investigation phase. No worktree exists initially.**
13
+
14
+ 🚫 **FORBIDDEN until a worktree is created:**
15
+ - Writing or editing any code files
16
+ - Creating new files in the main repository
17
+
18
+ ✅ **ALLOWED during planning phases:**
19
+ - Reading files to understand the codebase
20
+ - Running `jettypod` commands
21
+ - Asking the user questions
22
+ - Analyzing and planning
23
+
24
+ **Worktrees are created at specific steps:**
25
+ - `work prototype start` (Step 4) - for UX prototyping
26
+ - `work tests start` (Step 7) - for BDD test authoring
27
+ - `work start` (Step 13) - for chore implementation (invokes speed-mode)
28
+
29
+ **Only write files AFTER the relevant worktree command succeeds.**
30
+
10
31
  ## Instructions
11
32
 
12
33
  When this skill is activated, you are helping discover the best approach for a feature. Follow this structured approach: