jettypod 4.2.10 → 4.3.0
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/hooks/post-checkout +78 -3
- package/hooks/pre-push +33 -0
- package/jettypod.js +5 -1
- package/lib/claudemd.js +12 -4
- package/lib/current-work.js +97 -84
- package/lib/merge-lock.js +30 -0
- package/lib/session-writer.js +20 -0
- package/lib/worktree-facade.js +43 -0
- package/lib/worktree-manager.js +35 -0
- package/package.json +1 -1
- package/skills-templates/feature-planning/SKILL.md +155 -105
- package/skills-templates/production-mode/SKILL.md +4 -7
- package/skills-templates/speed-mode/SKILL.md +471 -463
- package/skills-templates/stable-mode/SKILL.md +319 -371
- package/lib/migrations/016-worktree-sessions-table.js +0 -84
- package/lib/worktree-sessions.js +0 -186
|
@@ -1,84 +0,0 @@
|
|
|
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-sessions.js
DELETED
|
@@ -1,186 +0,0 @@
|
|
|
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
|
-
};
|