jettypod 4.4.9 → 4.4.11
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/apps/dashboard/README.md +36 -0
- package/apps/dashboard/app/favicon.ico +0 -0
- package/apps/dashboard/app/globals.css +122 -0
- package/apps/dashboard/app/layout.tsx +34 -0
- package/apps/dashboard/app/page.tsx +25 -0
- package/apps/dashboard/app/work/[id]/page.tsx +193 -0
- package/apps/dashboard/components/KanbanBoard.tsx +201 -0
- package/apps/dashboard/components/WorkItemTree.tsx +116 -0
- package/apps/dashboard/components.json +22 -0
- package/apps/dashboard/eslint.config.mjs +18 -0
- package/apps/dashboard/lib/db.ts +270 -0
- package/apps/dashboard/lib/utils.ts +6 -0
- package/apps/dashboard/next.config.ts +7 -0
- package/apps/dashboard/package.json +33 -0
- package/apps/dashboard/postcss.config.mjs +7 -0
- package/apps/dashboard/public/file.svg +1 -0
- package/apps/dashboard/public/globe.svg +1 -0
- package/apps/dashboard/public/next.svg +1 -0
- package/apps/dashboard/public/vercel.svg +1 -0
- package/apps/dashboard/public/window.svg +1 -0
- package/apps/dashboard/tsconfig.json +34 -0
- package/claude-hooks/enforce-skill-activation.js +225 -0
- package/jettypod.js +53 -0
- package/lib/current-work.js +10 -18
- package/lib/migrations/016-workflow-checkpoints-table.js +70 -0
- package/lib/migrations/017-backfill-epic-id.js +54 -0
- package/lib/planning-status.js +68 -0
- package/lib/workflow-checkpoint.js +204 -0
- package/package.json +7 -2
- package/skills-templates/chore-mode/SKILL.md +3 -0
- package/skills-templates/epic-planning/SKILL.md +225 -154
- package/skills-templates/feature-planning/SKILL.md +172 -87
- package/skills-templates/speed-mode/SKILL.md +161 -338
- package/skills-templates/stable-mode/SKILL.md +8 -2
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workflow Checkpoint - Persistence for Session Resume
|
|
3
|
+
*
|
|
4
|
+
* This module provides checkpoint creation, update, and retrieval for workflow
|
|
5
|
+
* state persistence. Enables resuming interrupted workflows across sessions.
|
|
6
|
+
*
|
|
7
|
+
* Core principles:
|
|
8
|
+
* - Branch-aware: checkpoints are tied to specific git branch context
|
|
9
|
+
* - Atomic updates: each step transition updates the checkpoint
|
|
10
|
+
* - Context preservation: stores full workflow state as JSON
|
|
11
|
+
* - Auto-cleanup: checkpoints are deleted on successful completion
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const { execSync } = require('child_process');
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Get the current git branch name
|
|
18
|
+
*
|
|
19
|
+
* @returns {string} Current branch name
|
|
20
|
+
*/
|
|
21
|
+
function getCurrentBranch() {
|
|
22
|
+
try {
|
|
23
|
+
return execSync('git branch --show-current', { encoding: 'utf8' }).trim();
|
|
24
|
+
} catch (err) {
|
|
25
|
+
return 'unknown';
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Create a new workflow checkpoint
|
|
31
|
+
*
|
|
32
|
+
* @param {Object} db - SQLite database connection
|
|
33
|
+
* @param {Object} options - Checkpoint options
|
|
34
|
+
* @param {string} options.skillName - Name of the skill (e.g., 'feature-planning')
|
|
35
|
+
* @param {number} options.currentStep - Current step number (1-indexed)
|
|
36
|
+
* @param {number} [options.totalSteps] - Total number of steps (optional)
|
|
37
|
+
* @param {Object} [options.context] - Workflow context to preserve (optional)
|
|
38
|
+
* @param {string} [options.branchName] - Git branch name (defaults to current branch)
|
|
39
|
+
* @param {number} [options.workItemId] - Associated work item ID (optional)
|
|
40
|
+
* @returns {Promise<Object>} Created checkpoint with id
|
|
41
|
+
*/
|
|
42
|
+
async function createCheckpoint(db, options) {
|
|
43
|
+
if (!db) {
|
|
44
|
+
throw new Error('Database connection required');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const {
|
|
48
|
+
skillName,
|
|
49
|
+
currentStep,
|
|
50
|
+
totalSteps = null,
|
|
51
|
+
context = {},
|
|
52
|
+
branchName = getCurrentBranch(),
|
|
53
|
+
workItemId = null
|
|
54
|
+
} = options;
|
|
55
|
+
|
|
56
|
+
const contextJson = JSON.stringify(context);
|
|
57
|
+
|
|
58
|
+
return new Promise((resolve, reject) => {
|
|
59
|
+
db.run(
|
|
60
|
+
`INSERT INTO workflow_checkpoints
|
|
61
|
+
(skill_name, current_step, total_steps, context_json, branch_name, work_item_id)
|
|
62
|
+
VALUES (?, ?, ?, ?, ?, ?)`,
|
|
63
|
+
[skillName, currentStep, totalSteps, contextJson, branchName, workItemId],
|
|
64
|
+
function(err) {
|
|
65
|
+
if (err) {
|
|
66
|
+
reject(err);
|
|
67
|
+
} else {
|
|
68
|
+
resolve({
|
|
69
|
+
id: this.lastID,
|
|
70
|
+
skill_name: skillName,
|
|
71
|
+
current_step: currentStep,
|
|
72
|
+
total_steps: totalSteps,
|
|
73
|
+
context_json: contextJson,
|
|
74
|
+
branch_name: branchName,
|
|
75
|
+
work_item_id: workItemId
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
);
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Update an existing workflow checkpoint
|
|
85
|
+
*
|
|
86
|
+
* @param {Object} db - SQLite database connection
|
|
87
|
+
* @param {string} branchName - Git branch name to identify checkpoint
|
|
88
|
+
* @param {Object} updates - Fields to update
|
|
89
|
+
* @param {number} [updates.currentStep] - New step number
|
|
90
|
+
* @param {number} [updates.totalSteps] - New total steps
|
|
91
|
+
* @param {Object} [updates.context] - New context (merged with existing)
|
|
92
|
+
* @returns {Promise<void>}
|
|
93
|
+
*/
|
|
94
|
+
async function updateCheckpoint(db, branchName, updates) {
|
|
95
|
+
const fields = [];
|
|
96
|
+
const values = [];
|
|
97
|
+
|
|
98
|
+
if (updates.currentStep !== undefined) {
|
|
99
|
+
fields.push('current_step = ?');
|
|
100
|
+
values.push(updates.currentStep);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (updates.totalSteps !== undefined) {
|
|
104
|
+
fields.push('total_steps = ?');
|
|
105
|
+
values.push(updates.totalSteps);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (updates.context !== undefined) {
|
|
109
|
+
fields.push('context_json = ?');
|
|
110
|
+
values.push(JSON.stringify(updates.context));
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Always update the updated_at timestamp
|
|
114
|
+
fields.push('updated_at = CURRENT_TIMESTAMP');
|
|
115
|
+
|
|
116
|
+
values.push(branchName);
|
|
117
|
+
|
|
118
|
+
return new Promise((resolve, reject) => {
|
|
119
|
+
db.run(
|
|
120
|
+
`UPDATE workflow_checkpoints SET ${fields.join(', ')} WHERE branch_name = ?`,
|
|
121
|
+
values,
|
|
122
|
+
(err) => {
|
|
123
|
+
if (err) {
|
|
124
|
+
reject(err);
|
|
125
|
+
} else {
|
|
126
|
+
resolve();
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
);
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Get checkpoint for a specific branch
|
|
135
|
+
*
|
|
136
|
+
* @param {Object} db - SQLite database connection
|
|
137
|
+
* @param {string} [branchName] - Git branch name (defaults to current branch)
|
|
138
|
+
* @returns {Promise<Object|null>} Checkpoint or null if not found
|
|
139
|
+
*/
|
|
140
|
+
async function getCheckpoint(db, branchName = getCurrentBranch()) {
|
|
141
|
+
return new Promise((resolve, reject) => {
|
|
142
|
+
db.get(
|
|
143
|
+
`SELECT * FROM workflow_checkpoints WHERE branch_name = ?`,
|
|
144
|
+
[branchName],
|
|
145
|
+
(err, row) => {
|
|
146
|
+
if (err) {
|
|
147
|
+
reject(err);
|
|
148
|
+
} else if (!row) {
|
|
149
|
+
resolve(null);
|
|
150
|
+
} else {
|
|
151
|
+
// Parse context_json back to object
|
|
152
|
+
resolve({
|
|
153
|
+
...row,
|
|
154
|
+
context: row.context_json ? JSON.parse(row.context_json) : {}
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
);
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Delete checkpoint for a specific branch (on successful completion)
|
|
164
|
+
*
|
|
165
|
+
* @param {Object} db - SQLite database connection
|
|
166
|
+
* @param {string} [branchName] - Git branch name (defaults to current branch)
|
|
167
|
+
* @returns {Promise<void>}
|
|
168
|
+
*/
|
|
169
|
+
async function deleteCheckpoint(db, branchName = getCurrentBranch()) {
|
|
170
|
+
return new Promise((resolve, reject) => {
|
|
171
|
+
db.run(
|
|
172
|
+
`DELETE FROM workflow_checkpoints WHERE branch_name = ?`,
|
|
173
|
+
[branchName],
|
|
174
|
+
(err) => {
|
|
175
|
+
if (err) {
|
|
176
|
+
reject(err);
|
|
177
|
+
} else {
|
|
178
|
+
resolve();
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
);
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Check if a checkpoint exists for the current branch
|
|
187
|
+
*
|
|
188
|
+
* @param {Object} db - SQLite database connection
|
|
189
|
+
* @param {string} [branchName] - Git branch name (defaults to current branch)
|
|
190
|
+
* @returns {Promise<boolean>} True if checkpoint exists
|
|
191
|
+
*/
|
|
192
|
+
async function hasCheckpoint(db, branchName = getCurrentBranch()) {
|
|
193
|
+
const checkpoint = await getCheckpoint(db, branchName);
|
|
194
|
+
return checkpoint !== null;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
module.exports = {
|
|
198
|
+
createCheckpoint,
|
|
199
|
+
updateCheckpoint,
|
|
200
|
+
getCheckpoint,
|
|
201
|
+
deleteCheckpoint,
|
|
202
|
+
hasCheckpoint,
|
|
203
|
+
getCurrentBranch
|
|
204
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jettypod",
|
|
3
|
-
"version": "4.4.
|
|
3
|
+
"version": "4.4.11",
|
|
4
4
|
"description": "AI-powered development workflow manager with TDD, BDD, and automatic test generation",
|
|
5
5
|
"main": "jettypod.js",
|
|
6
6
|
"bin": {
|
|
@@ -34,10 +34,14 @@
|
|
|
34
34
|
"test:unit": "NODE_ENV=test jest",
|
|
35
35
|
"test:unit:watch": "NODE_ENV=test jest --watch",
|
|
36
36
|
"test:cleanup": "node scripts/test-cleanup.js",
|
|
37
|
-
"prepublishOnly": "echo '🔄 Syncing skills to templates...' && cp -r .claude/skills/* skills-templates/ && echo '✅ Skills synced'"
|
|
37
|
+
"prepublishOnly": "echo '🔄 Syncing skills to templates...' && cp -r .claude/skills/* skills-templates/ && echo '✅ Skills synced'",
|
|
38
|
+
"dashboard": "npm run dev --prefix apps/dashboard",
|
|
39
|
+
"dashboard:build": "npm run build --prefix apps/dashboard",
|
|
40
|
+
"dashboard:start": "npm run start --prefix apps/dashboard"
|
|
38
41
|
},
|
|
39
42
|
"devDependencies": {
|
|
40
43
|
"@cucumber/cucumber": "^10.0.1",
|
|
44
|
+
"@types/better-sqlite3": "^7.6.13",
|
|
41
45
|
"chai": "^6.0.1",
|
|
42
46
|
"jest": "^30.1.3"
|
|
43
47
|
},
|
|
@@ -54,6 +58,7 @@
|
|
|
54
58
|
"coverageDirectory": "coverage"
|
|
55
59
|
},
|
|
56
60
|
"dependencies": {
|
|
61
|
+
"better-sqlite3": "^12.5.0",
|
|
57
62
|
"chalk": "^4.1.2",
|
|
58
63
|
"sqlite3": "^5.1.7"
|
|
59
64
|
},
|
|
@@ -304,6 +304,9 @@ git add .
|
|
|
304
304
|
git commit -m "chore: [brief description]"
|
|
305
305
|
git push
|
|
306
306
|
|
|
307
|
+
# Switch to main repo before merging (merge cannot run from inside worktree)
|
|
308
|
+
cd $(git rev-parse --git-common-dir)/..
|
|
309
|
+
|
|
307
310
|
# Merge to main (auto-marks chore done)
|
|
308
311
|
jettypod work merge
|
|
309
312
|
```
|