jettypod 4.4.10 → 4.4.12

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.
Files changed (32) hide show
  1. package/apps/dashboard/README.md +36 -0
  2. package/apps/dashboard/app/favicon.ico +0 -0
  3. package/apps/dashboard/app/globals.css +122 -0
  4. package/apps/dashboard/app/layout.tsx +34 -0
  5. package/apps/dashboard/app/page.tsx +25 -0
  6. package/apps/dashboard/app/work/[id]/page.tsx +193 -0
  7. package/apps/dashboard/components/KanbanBoard.tsx +201 -0
  8. package/apps/dashboard/components/WorkItemTree.tsx +116 -0
  9. package/apps/dashboard/components.json +22 -0
  10. package/apps/dashboard/eslint.config.mjs +18 -0
  11. package/apps/dashboard/lib/db.ts +270 -0
  12. package/apps/dashboard/lib/utils.ts +6 -0
  13. package/apps/dashboard/next.config.ts +7 -0
  14. package/apps/dashboard/package.json +33 -0
  15. package/apps/dashboard/postcss.config.mjs +7 -0
  16. package/apps/dashboard/public/file.svg +1 -0
  17. package/apps/dashboard/public/globe.svg +1 -0
  18. package/apps/dashboard/public/next.svg +1 -0
  19. package/apps/dashboard/public/vercel.svg +1 -0
  20. package/apps/dashboard/public/window.svg +1 -0
  21. package/apps/dashboard/tsconfig.json +34 -0
  22. package/jettypod.js +41 -0
  23. package/lib/current-work.js +10 -18
  24. package/lib/migrations/016-workflow-checkpoints-table.js +70 -0
  25. package/lib/migrations/017-backfill-epic-id.js +54 -0
  26. package/lib/workflow-checkpoint.js +204 -0
  27. package/package.json +7 -2
  28. package/skills-templates/chore-mode/SKILL.md +3 -0
  29. package/skills-templates/epic-planning/SKILL.md +225 -154
  30. package/skills-templates/feature-planning/SKILL.md +172 -87
  31. package/skills-templates/speed-mode/SKILL.md +161 -338
  32. package/skills-templates/stable-mode/SKILL.md +150 -176
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Migration: Create workflow_checkpoints table
3
+ *
4
+ * Purpose: Enable persistence of workflow state across sessions so interrupted
5
+ * workflows can be resumed. Stores skill name, current step, and context JSON.
6
+ *
7
+ * Why this is critical:
8
+ * - Crash recovery - resume workflows after unexpected session termination
9
+ * - Branch-aware - checkpoints are tied to specific worktree/branch context
10
+ * - Context preservation - stores full workflow state as JSON for resume
11
+ */
12
+
13
+ module.exports = {
14
+ id: '016-workflow-checkpoints-table',
15
+ description: 'Create workflow_checkpoints table for session resume capability',
16
+
17
+ async up(db) {
18
+ return new Promise((resolve, reject) => {
19
+ db.run(`
20
+ CREATE TABLE IF NOT EXISTS workflow_checkpoints (
21
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
22
+ skill_name TEXT NOT NULL,
23
+ current_step INTEGER NOT NULL,
24
+ total_steps INTEGER,
25
+ context_json TEXT,
26
+ branch_name TEXT NOT NULL,
27
+ work_item_id INTEGER,
28
+ created_at TEXT DEFAULT CURRENT_TIMESTAMP,
29
+ updated_at TEXT DEFAULT CURRENT_TIMESTAMP
30
+ )
31
+ `, (err) => {
32
+ if (err) {
33
+ return reject(err);
34
+ }
35
+
36
+ // Create index on branch_name for quick lookup by current branch
37
+ db.run(`
38
+ CREATE INDEX IF NOT EXISTS idx_workflow_checkpoints_branch
39
+ ON workflow_checkpoints(branch_name)
40
+ `, (err) => {
41
+ if (err) {
42
+ return reject(err);
43
+ }
44
+
45
+ resolve();
46
+ });
47
+ });
48
+ });
49
+ },
50
+
51
+ async down(db) {
52
+ return new Promise((resolve, reject) => {
53
+ // Drop index first
54
+ db.run('DROP INDEX IF EXISTS idx_workflow_checkpoints_branch', (err) => {
55
+ if (err) {
56
+ return reject(err);
57
+ }
58
+
59
+ // Drop table
60
+ db.run('DROP TABLE IF EXISTS workflow_checkpoints', (err) => {
61
+ if (err) {
62
+ return reject(err);
63
+ }
64
+
65
+ resolve();
66
+ });
67
+ });
68
+ });
69
+ }
70
+ };
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Migration 017: Backfill epic_id field
3
+ *
4
+ * The epic_id field provides a direct reference to the top-level epic,
5
+ * avoiding tree traversal when querying items by epic.
6
+ *
7
+ * This migration backfills epic_id for existing items:
8
+ * - Features: epic_id = parent_id (where parent is an epic)
9
+ * - Chores: epic_id = parent's epic_id (inherit from parent)
10
+ */
11
+
12
+ module.exports = {
13
+ id: '017-backfill-epic-id',
14
+
15
+ up: (db) => {
16
+ return new Promise((resolve, reject) => {
17
+ db.serialize(() => {
18
+ // Backfill epic_id for features (parent is epic)
19
+ db.run(`
20
+ UPDATE work_items
21
+ SET epic_id = parent_id
22
+ WHERE type = 'feature'
23
+ AND parent_id IS NOT NULL
24
+ AND epic_id IS NULL
25
+ AND (SELECT type FROM work_items p WHERE p.id = work_items.parent_id) = 'epic'
26
+ `, (err) => {
27
+ if (err) return reject(err);
28
+
29
+ // Backfill epic_id for chores (inherit from parent)
30
+ db.run(`
31
+ UPDATE work_items
32
+ SET epic_id = (SELECT epic_id FROM work_items p WHERE p.id = work_items.parent_id)
33
+ WHERE type = 'chore'
34
+ AND parent_id IS NOT NULL
35
+ AND epic_id IS NULL
36
+ `, (err) => {
37
+ if (err) return reject(err);
38
+ resolve();
39
+ });
40
+ });
41
+ });
42
+ });
43
+ },
44
+
45
+ down: (db) => {
46
+ return new Promise((resolve, reject) => {
47
+ // Clear all epic_id values
48
+ db.run(`UPDATE work_items SET epic_id = NULL`, (err) => {
49
+ if (err) return reject(err);
50
+ resolve();
51
+ });
52
+ });
53
+ }
54
+ };
@@ -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.10",
3
+ "version": "4.4.12",
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
  ```