jettypod 4.1.4 → 4.1.6
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/cucumber.js +0 -1
- package/lib/current-work.js +104 -52
- package/lib/migrations/016-worktree-sessions-table.js +84 -0
- package/lib/worktree-manager.js +27 -2
- package/lib/worktree-sessions.js +186 -0
- package/package.json +1 -1
- package/skills-templates/feature-planning/SKILL.md +68 -180
- package/skills-templates/speed-mode/SKILL.md +224 -93
- package/skills-templates/stable-mode/SKILL.md +189 -158
package/cucumber.js
CHANGED
package/lib/current-work.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const path = require('path');
|
|
3
|
+
const { getDb } = require('./database');
|
|
4
|
+
const { getSessionByWorktreePath, getAllActiveSessions } = require('./worktree-sessions');
|
|
3
5
|
|
|
4
6
|
/**
|
|
5
7
|
* Get path to current-work.json file
|
|
@@ -10,37 +12,100 @@ function getCurrentWorkPath() {
|
|
|
10
12
|
}
|
|
11
13
|
|
|
12
14
|
/**
|
|
13
|
-
* Get current work item from
|
|
15
|
+
* Get current work item from worktree session database
|
|
14
16
|
* @returns {Object|null} Current work item or null if not set
|
|
15
|
-
* @throws {Error} If file exists but cannot be read due to permissions
|
|
16
17
|
*/
|
|
17
|
-
function getCurrentWork() {
|
|
18
|
-
const
|
|
18
|
+
async function getCurrentWork() {
|
|
19
|
+
const worktreePath = process.cwd();
|
|
20
|
+
const db = getDb();
|
|
19
21
|
|
|
20
|
-
|
|
22
|
+
// Query worktree session and join with work_items
|
|
23
|
+
let row;
|
|
24
|
+
try {
|
|
25
|
+
row = await new Promise((resolve, reject) => {
|
|
26
|
+
db.get(
|
|
27
|
+
`SELECT
|
|
28
|
+
wi.id, wi.title, wi.type, wi.status, wi.parent_id, wi.epic_id,
|
|
29
|
+
parent.title as parent_title,
|
|
30
|
+
epic.title as epic_title
|
|
31
|
+
FROM worktree_sessions ws
|
|
32
|
+
JOIN work_items wi ON ws.work_item_id = wi.id
|
|
33
|
+
LEFT JOIN work_items parent ON wi.parent_id = parent.id
|
|
34
|
+
LEFT JOIN work_items epic ON wi.epic_id = epic.id
|
|
35
|
+
WHERE ws.worktree_path = ?`,
|
|
36
|
+
[worktreePath],
|
|
37
|
+
(err, row) => {
|
|
38
|
+
if (err) {
|
|
39
|
+
return reject(err);
|
|
40
|
+
}
|
|
41
|
+
resolve(row);
|
|
42
|
+
}
|
|
43
|
+
);
|
|
44
|
+
});
|
|
45
|
+
} catch (dbErr) {
|
|
46
|
+
// Handle database errors gracefully - log but don't crash
|
|
47
|
+
console.error('Database error querying current work:', dbErr.message);
|
|
48
|
+
// Return null instead of throwing - graceful degradation
|
|
21
49
|
return null;
|
|
22
50
|
}
|
|
23
51
|
|
|
24
|
-
|
|
25
|
-
const content = fs.readFileSync(currentWorkPath, 'utf-8');
|
|
26
|
-
const workItem = JSON.parse(content);
|
|
27
|
-
|
|
52
|
+
if (row) {
|
|
28
53
|
// Validate required fields
|
|
29
|
-
if (!
|
|
30
|
-
console.warn('Warning:
|
|
54
|
+
if (!row.id || !row.title || !row.type) {
|
|
55
|
+
console.warn('Warning: Work item is missing required fields, ignoring');
|
|
31
56
|
return null;
|
|
32
57
|
}
|
|
58
|
+
return row;
|
|
59
|
+
}
|
|
33
60
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
61
|
+
// No session found - check if we should warn about main branch
|
|
62
|
+
try {
|
|
63
|
+
const allSessions = await getAllActiveSessions();
|
|
64
|
+
if (allSessions.length > 0) {
|
|
65
|
+
// Get all work items in ONE query using IN clause
|
|
66
|
+
const ids = allSessions.map(s => s.work_item_id);
|
|
67
|
+
const placeholders = ids.map(() => '?').join(',');
|
|
68
|
+
|
|
69
|
+
let workItems;
|
|
70
|
+
try {
|
|
71
|
+
workItems = await new Promise((resolve, reject) => {
|
|
72
|
+
db.all(
|
|
73
|
+
`SELECT id, title FROM work_items WHERE id IN (${placeholders})`,
|
|
74
|
+
ids,
|
|
75
|
+
(err, rows) => {
|
|
76
|
+
if (err) reject(err);
|
|
77
|
+
else resolve(rows || []);
|
|
78
|
+
}
|
|
79
|
+
);
|
|
80
|
+
});
|
|
81
|
+
} catch (workItemErr) {
|
|
82
|
+
// Database error getting work items - log but continue
|
|
83
|
+
console.error('Database error getting work items:', workItemErr.message);
|
|
84
|
+
workItems = [];
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Create map of id -> title
|
|
88
|
+
const titleMap = {};
|
|
89
|
+
for (const wi of workItems) {
|
|
90
|
+
titleMap[wi.id] = wi.title;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Display warnings
|
|
94
|
+
console.warn('No active work in main branch');
|
|
95
|
+
console.warn('\nActive work in other worktrees:');
|
|
96
|
+
for (const session of allSessions) {
|
|
97
|
+
const title = titleMap[session.work_item_id];
|
|
98
|
+
if (title) {
|
|
99
|
+
console.warn(` • ${title} (${session.worktree_path})`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
38
102
|
}
|
|
39
|
-
|
|
40
|
-
//
|
|
41
|
-
console.
|
|
42
|
-
return null;
|
|
103
|
+
} catch (listErr) {
|
|
104
|
+
// Ignore listing errors - gracefully fail
|
|
105
|
+
console.error('Error listing active sessions:', listErr.message);
|
|
43
106
|
}
|
|
107
|
+
|
|
108
|
+
return null;
|
|
44
109
|
}
|
|
45
110
|
|
|
46
111
|
/**
|
|
@@ -71,54 +136,41 @@ function validateWorkItem(workItem) {
|
|
|
71
136
|
}
|
|
72
137
|
|
|
73
138
|
/**
|
|
74
|
-
* Set current work item
|
|
139
|
+
* Set current work item in worktree session database
|
|
75
140
|
* @param {Object} workItem - Work item to set as current
|
|
76
|
-
* @throws {Error} If workItem is invalid
|
|
141
|
+
* @throws {Error} If workItem is invalid
|
|
77
142
|
*/
|
|
78
|
-
function setCurrentWork(workItem) {
|
|
143
|
+
async function setCurrentWork(workItem) {
|
|
79
144
|
validateWorkItem(workItem);
|
|
80
145
|
|
|
81
|
-
const
|
|
82
|
-
const
|
|
146
|
+
const { createOrUpdateSession } = require('./worktree-sessions');
|
|
147
|
+
const { execSync } = require('child_process');
|
|
148
|
+
const worktreePath = process.cwd();
|
|
83
149
|
|
|
150
|
+
// Get current branch name
|
|
151
|
+
let branchName;
|
|
84
152
|
try {
|
|
85
|
-
|
|
86
|
-
if (!fs.existsSync(jettypodDir)) {
|
|
87
|
-
fs.mkdirSync(jettypodDir, { recursive: true });
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// Check write permission
|
|
91
|
-
if (fs.existsSync(jettypodDir)) {
|
|
92
|
-
fs.accessSync(jettypodDir, fs.constants.W_OK);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
fs.writeFileSync(currentWorkPath, JSON.stringify(workItem, null, 2));
|
|
153
|
+
branchName = execSync('git rev-parse --abbrev-ref HEAD', { encoding: 'utf-8' }).trim();
|
|
96
154
|
} catch (err) {
|
|
97
|
-
|
|
98
|
-
throw new Error(`No write permission for directory: ${jettypodDir}`);
|
|
99
|
-
}
|
|
100
|
-
throw new Error(`Failed to write current work file: ${err.message}`);
|
|
155
|
+
branchName = 'unknown';
|
|
101
156
|
}
|
|
157
|
+
|
|
158
|
+
// Create or update worktree session in database
|
|
159
|
+
await createOrUpdateSession(worktreePath, workItem.id, branchName);
|
|
102
160
|
}
|
|
103
161
|
|
|
104
162
|
/**
|
|
105
|
-
* Clear current work item
|
|
106
|
-
* @throws {Error} If
|
|
163
|
+
* Clear current work item from worktree session database
|
|
164
|
+
* @throws {Error} If session cannot be deleted
|
|
107
165
|
*/
|
|
108
|
-
function clearCurrentWork() {
|
|
109
|
-
const
|
|
110
|
-
|
|
111
|
-
if (!fs.existsSync(currentWorkPath)) {
|
|
112
|
-
return;
|
|
113
|
-
}
|
|
166
|
+
async function clearCurrentWork() {
|
|
167
|
+
const { deleteSession } = require('./worktree-sessions');
|
|
168
|
+
const worktreePath = process.cwd();
|
|
114
169
|
|
|
115
170
|
try {
|
|
116
|
-
|
|
171
|
+
await deleteSession(worktreePath);
|
|
117
172
|
} catch (err) {
|
|
118
|
-
|
|
119
|
-
throw new Error(`No permission to delete current work file: ${currentWorkPath}`);
|
|
120
|
-
}
|
|
121
|
-
throw new Error(`Failed to clear current work file: ${err.message}`);
|
|
173
|
+
throw new Error(`Failed to clear current work session: ${err.message}`);
|
|
122
174
|
}
|
|
123
175
|
}
|
|
124
176
|
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Migration: Create worktree_sessions table
|
|
3
|
+
*
|
|
4
|
+
* Purpose: Track which work item is active in each worktree to support multiple
|
|
5
|
+
* concurrent Claude Code instances working on different features simultaneously.
|
|
6
|
+
*
|
|
7
|
+
* Why this is critical:
|
|
8
|
+
* - Enable true concurrent development with multiple worktrees
|
|
9
|
+
* - Prevent confusion when Claude switches between worktrees
|
|
10
|
+
* - Provide centralized visibility of what's active where
|
|
11
|
+
* - Support automatic cleanup when work items are completed
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
module.exports = {
|
|
15
|
+
id: '016-worktree-sessions-table',
|
|
16
|
+
description: 'Create worktree_sessions table for multi-worktree current work tracking',
|
|
17
|
+
|
|
18
|
+
async up(db) {
|
|
19
|
+
return new Promise((resolve, reject) => {
|
|
20
|
+
db.run(`
|
|
21
|
+
CREATE TABLE IF NOT EXISTS worktree_sessions (
|
|
22
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
23
|
+
worktree_path TEXT UNIQUE NOT NULL,
|
|
24
|
+
work_item_id INTEGER NOT NULL,
|
|
25
|
+
branch_name TEXT NOT NULL,
|
|
26
|
+
last_activity DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
27
|
+
FOREIGN KEY (work_item_id) REFERENCES work_items(id)
|
|
28
|
+
)
|
|
29
|
+
`, (err) => {
|
|
30
|
+
if (err) {
|
|
31
|
+
return reject(err);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Create index on worktree_path for fast lookups
|
|
35
|
+
db.run(`
|
|
36
|
+
CREATE INDEX IF NOT EXISTS idx_worktree_sessions_path
|
|
37
|
+
ON worktree_sessions(worktree_path)
|
|
38
|
+
`, (err) => {
|
|
39
|
+
if (err) {
|
|
40
|
+
return reject(err);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Create index on work_item_id for reverse lookups
|
|
44
|
+
db.run(`
|
|
45
|
+
CREATE INDEX IF NOT EXISTS idx_worktree_sessions_work_item
|
|
46
|
+
ON worktree_sessions(work_item_id)
|
|
47
|
+
`, (err) => {
|
|
48
|
+
if (err) {
|
|
49
|
+
return reject(err);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
resolve();
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
},
|
|
58
|
+
|
|
59
|
+
async down(db) {
|
|
60
|
+
return new Promise((resolve, reject) => {
|
|
61
|
+
// Drop indexes first
|
|
62
|
+
db.run('DROP INDEX IF EXISTS idx_worktree_sessions_work_item', (err) => {
|
|
63
|
+
if (err) {
|
|
64
|
+
return reject(err);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
db.run('DROP INDEX IF EXISTS idx_worktree_sessions_path', (err) => {
|
|
68
|
+
if (err) {
|
|
69
|
+
return reject(err);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Drop table
|
|
73
|
+
db.run('DROP TABLE IF EXISTS worktree_sessions', (err) => {
|
|
74
|
+
if (err) {
|
|
75
|
+
return reject(err);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
resolve();
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
};
|
package/lib/worktree-manager.js
CHANGED
|
@@ -100,9 +100,34 @@ async function createWorktree(workItem, options = {}) {
|
|
|
100
100
|
});
|
|
101
101
|
|
|
102
102
|
// Step 2: Create git worktree
|
|
103
|
-
//
|
|
103
|
+
// Branch from the default branch (main, master, etc.) to avoid nested worktrees
|
|
104
104
|
try {
|
|
105
|
-
|
|
105
|
+
// Detect the default branch name
|
|
106
|
+
let defaultBranch;
|
|
107
|
+
try {
|
|
108
|
+
// Try to get the default branch from git config
|
|
109
|
+
defaultBranch = execSync('git symbolic-ref refs/remotes/origin/HEAD', {
|
|
110
|
+
cwd: gitRoot,
|
|
111
|
+
encoding: 'utf8',
|
|
112
|
+
stdio: ['pipe', 'pipe', 'pipe']
|
|
113
|
+
}).trim().replace('refs/remotes/origin/', '');
|
|
114
|
+
} catch {
|
|
115
|
+
// Fallback: check which common branch names exist
|
|
116
|
+
try {
|
|
117
|
+
execSync('git rev-parse --verify main', { cwd: gitRoot, stdio: 'pipe' });
|
|
118
|
+
defaultBranch = 'main';
|
|
119
|
+
} catch {
|
|
120
|
+
try {
|
|
121
|
+
execSync('git rev-parse --verify master', { cwd: gitRoot, stdio: 'pipe' });
|
|
122
|
+
defaultBranch = 'master';
|
|
123
|
+
} catch {
|
|
124
|
+
// Last resort: use HEAD
|
|
125
|
+
defaultBranch = 'HEAD';
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
execSync(`git worktree add -b "${branchName}" "${worktreePath}" ${defaultBranch}`, {
|
|
106
131
|
cwd: gitRoot,
|
|
107
132
|
stdio: 'pipe'
|
|
108
133
|
});
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Worktree Session Management
|
|
3
|
+
*
|
|
4
|
+
* CRUD operations for tracking which work item is active in each worktree.
|
|
5
|
+
* Enables multiple concurrent Claude Code instances working in different worktrees.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const { getDb } = require('./database');
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Create or update a worktree session
|
|
12
|
+
*
|
|
13
|
+
* @param {string} worktreePath - Absolute path to the worktree
|
|
14
|
+
* @param {number} workItemId - ID of the work item
|
|
15
|
+
* @param {string} branchName - Git branch name
|
|
16
|
+
* @returns {Promise<void>}
|
|
17
|
+
*/
|
|
18
|
+
function createOrUpdateSession(worktreePath, workItemId, branchName) {
|
|
19
|
+
const db = getDb();
|
|
20
|
+
|
|
21
|
+
return new Promise((resolve, reject) => {
|
|
22
|
+
db.run(
|
|
23
|
+
`INSERT OR REPLACE INTO worktree_sessions
|
|
24
|
+
(worktree_path, work_item_id, branch_name, last_activity)
|
|
25
|
+
VALUES (?, ?, ?, datetime('now'))`,
|
|
26
|
+
[worktreePath, workItemId, branchName],
|
|
27
|
+
(err) => {
|
|
28
|
+
if (err) {
|
|
29
|
+
// Provide user-friendly error messages based on error type
|
|
30
|
+
if (err.code === 'SQLITE_BUSY') {
|
|
31
|
+
return reject(new Error('Database is locked by another process. Please try again in a moment.'));
|
|
32
|
+
} else if (err.code === 'SQLITE_CORRUPT') {
|
|
33
|
+
return reject(new Error('Database is corrupted. Please run: jettypod init --reset'));
|
|
34
|
+
} else if (err.code === 'SQLITE_CANTOPEN') {
|
|
35
|
+
return reject(new Error('Cannot open database. Check file permissions or run: jettypod init'));
|
|
36
|
+
} else if (err.code === 'SQLITE_READONLY') {
|
|
37
|
+
return reject(new Error('Database is read-only. Check file permissions.'));
|
|
38
|
+
}
|
|
39
|
+
// Generic database error
|
|
40
|
+
console.error('Database error in createOrUpdateSession:', err);
|
|
41
|
+
return reject(new Error(`Failed to create/update worktree session: ${err.message}`));
|
|
42
|
+
}
|
|
43
|
+
resolve();
|
|
44
|
+
}
|
|
45
|
+
);
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Get session by worktree path
|
|
51
|
+
*
|
|
52
|
+
* @param {string} worktreePath - Absolute path to the worktree
|
|
53
|
+
* @returns {Promise<object|null>} Session object or null if not found
|
|
54
|
+
*/
|
|
55
|
+
function getSessionByWorktreePath(worktreePath) {
|
|
56
|
+
const db = getDb();
|
|
57
|
+
|
|
58
|
+
return new Promise((resolve, reject) => {
|
|
59
|
+
db.get(
|
|
60
|
+
`SELECT * FROM worktree_sessions WHERE worktree_path = ?`,
|
|
61
|
+
[worktreePath],
|
|
62
|
+
(err, row) => {
|
|
63
|
+
if (err) {
|
|
64
|
+
// Provide user-friendly error messages
|
|
65
|
+
if (err.code === 'SQLITE_BUSY') {
|
|
66
|
+
return reject(new Error('Database is locked. Please try again in a moment.'));
|
|
67
|
+
} else if (err.code === 'SQLITE_CORRUPT') {
|
|
68
|
+
return reject(new Error('Database is corrupted. Please run: jettypod init --reset'));
|
|
69
|
+
} else if (err.code === 'SQLITE_CANTOPEN') {
|
|
70
|
+
return reject(new Error('Cannot open database. Run: jettypod init'));
|
|
71
|
+
}
|
|
72
|
+
console.error('Database error in getSessionByWorktreePath:', err);
|
|
73
|
+
return reject(new Error(`Failed to get worktree session: ${err.message}`));
|
|
74
|
+
}
|
|
75
|
+
resolve(row || null);
|
|
76
|
+
}
|
|
77
|
+
);
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Get all active worktree sessions
|
|
83
|
+
*
|
|
84
|
+
* @returns {Promise<Array>} Array of session objects
|
|
85
|
+
*/
|
|
86
|
+
function getAllActiveSessions() {
|
|
87
|
+
const db = getDb();
|
|
88
|
+
|
|
89
|
+
return new Promise((resolve, reject) => {
|
|
90
|
+
db.all(
|
|
91
|
+
`SELECT * FROM worktree_sessions ORDER BY last_activity DESC`,
|
|
92
|
+
[],
|
|
93
|
+
(err, rows) => {
|
|
94
|
+
if (err) {
|
|
95
|
+
// Provide user-friendly error messages
|
|
96
|
+
if (err.code === 'SQLITE_BUSY') {
|
|
97
|
+
return reject(new Error('Database is locked. Please try again in a moment.'));
|
|
98
|
+
} else if (err.code === 'SQLITE_CORRUPT') {
|
|
99
|
+
return reject(new Error('Database is corrupted. Please run: jettypod init --reset'));
|
|
100
|
+
} else if (err.code === 'SQLITE_CANTOPEN') {
|
|
101
|
+
return reject(new Error('Cannot open database. Run: jettypod init'));
|
|
102
|
+
}
|
|
103
|
+
console.error('Database error in getAllActiveSessions:', err);
|
|
104
|
+
return reject(new Error(`Failed to get active sessions: ${err.message}`));
|
|
105
|
+
}
|
|
106
|
+
resolve(rows || []);
|
|
107
|
+
}
|
|
108
|
+
);
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Delete session by worktree path
|
|
114
|
+
*
|
|
115
|
+
* @param {string} worktreePath - Absolute path to the worktree
|
|
116
|
+
* @returns {Promise<void>}
|
|
117
|
+
*/
|
|
118
|
+
function deleteSession(worktreePath) {
|
|
119
|
+
const db = getDb();
|
|
120
|
+
|
|
121
|
+
return new Promise((resolve, reject) => {
|
|
122
|
+
db.run(
|
|
123
|
+
`DELETE FROM worktree_sessions WHERE worktree_path = ?`,
|
|
124
|
+
[worktreePath],
|
|
125
|
+
(err) => {
|
|
126
|
+
if (err) {
|
|
127
|
+
// Provide user-friendly error messages
|
|
128
|
+
if (err.code === 'SQLITE_BUSY') {
|
|
129
|
+
return reject(new Error('Database is locked. Please try again in a moment.'));
|
|
130
|
+
} else if (err.code === 'SQLITE_CORRUPT') {
|
|
131
|
+
return reject(new Error('Database is corrupted. Please run: jettypod init --reset'));
|
|
132
|
+
} else if (err.code === 'SQLITE_CANTOPEN') {
|
|
133
|
+
return reject(new Error('Cannot open database. Run: jettypod init'));
|
|
134
|
+
} else if (err.code === 'SQLITE_READONLY') {
|
|
135
|
+
return reject(new Error('Database is read-only. Check file permissions.'));
|
|
136
|
+
}
|
|
137
|
+
console.error('Database error in deleteSession:', err);
|
|
138
|
+
return reject(new Error(`Failed to delete worktree session: ${err.message}`));
|
|
139
|
+
}
|
|
140
|
+
resolve();
|
|
141
|
+
}
|
|
142
|
+
);
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Delete session by work item ID
|
|
148
|
+
*
|
|
149
|
+
* @param {number} workItemId - ID of the work item
|
|
150
|
+
* @returns {Promise<void>}
|
|
151
|
+
*/
|
|
152
|
+
function deleteSessionByWorkItem(workItemId) {
|
|
153
|
+
const db = getDb();
|
|
154
|
+
|
|
155
|
+
return new Promise((resolve, reject) => {
|
|
156
|
+
db.run(
|
|
157
|
+
`DELETE FROM worktree_sessions WHERE work_item_id = ?`,
|
|
158
|
+
[workItemId],
|
|
159
|
+
(err) => {
|
|
160
|
+
if (err) {
|
|
161
|
+
// Provide user-friendly error messages
|
|
162
|
+
if (err.code === 'SQLITE_BUSY') {
|
|
163
|
+
return reject(new Error('Database is locked. Please try again in a moment.'));
|
|
164
|
+
} else if (err.code === 'SQLITE_CORRUPT') {
|
|
165
|
+
return reject(new Error('Database is corrupted. Please run: jettypod init --reset'));
|
|
166
|
+
} else if (err.code === 'SQLITE_CANTOPEN') {
|
|
167
|
+
return reject(new Error('Cannot open database. Run: jettypod init'));
|
|
168
|
+
} else if (err.code === 'SQLITE_READONLY') {
|
|
169
|
+
return reject(new Error('Database is read-only. Check file permissions.'));
|
|
170
|
+
}
|
|
171
|
+
console.error('Database error in deleteSessionByWorkItem:', err);
|
|
172
|
+
return reject(new Error(`Failed to delete session by work item: ${err.message}`));
|
|
173
|
+
}
|
|
174
|
+
resolve();
|
|
175
|
+
}
|
|
176
|
+
);
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
module.exports = {
|
|
181
|
+
createOrUpdateSession,
|
|
182
|
+
getSessionByWorktreePath,
|
|
183
|
+
getAllActiveSessions,
|
|
184
|
+
deleteSession,
|
|
185
|
+
deleteSessionByWorkItem
|
|
186
|
+
};
|