jettypod 4.4.36 → 4.4.38
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/lib/database.js +61 -20
- package/lib/merge-lock.js +17 -32
- package/lib/preflight-validator.js +66 -0
- package/lib/production-standards-reader.js +3 -3
- package/lib/work-commands/index.js +37 -5
- package/lib/work-tracking/index.js +145 -104
- package/package.json +1 -1
package/lib/database.js
CHANGED
|
@@ -100,10 +100,16 @@ function getDb() {
|
|
|
100
100
|
});
|
|
101
101
|
|
|
102
102
|
// Enable WAL mode for better concurrency and avoid locking issues
|
|
103
|
-
db.run('PRAGMA journal_mode = WAL')
|
|
104
|
-
|
|
103
|
+
db.run('PRAGMA journal_mode = WAL', (err) => {
|
|
104
|
+
if (err) console.warn('PRAGMA journal_mode failed:', err.message);
|
|
105
|
+
});
|
|
106
|
+
db.run('PRAGMA synchronous = NORMAL', (err) => {
|
|
107
|
+
if (err) console.warn('PRAGMA synchronous failed:', err.message);
|
|
108
|
+
});
|
|
105
109
|
// Reduce busy timeout to fail faster in tests
|
|
106
|
-
db.run('PRAGMA busy_timeout = 5000')
|
|
110
|
+
db.run('PRAGMA busy_timeout = 5000', (err) => {
|
|
111
|
+
if (err) console.warn('PRAGMA busy_timeout failed:', err.message);
|
|
112
|
+
});
|
|
107
113
|
|
|
108
114
|
initSchema();
|
|
109
115
|
} catch (err) {
|
|
@@ -141,6 +147,7 @@ function initSchema() {
|
|
|
141
147
|
phase TEXT,
|
|
142
148
|
prototype_files TEXT,
|
|
143
149
|
discovery_winner TEXT,
|
|
150
|
+
discovery_rationale TEXT,
|
|
144
151
|
scenario_file TEXT,
|
|
145
152
|
completed_at TEXT,
|
|
146
153
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
@@ -196,29 +203,30 @@ function initSchema() {
|
|
|
196
203
|
}
|
|
197
204
|
});
|
|
198
205
|
|
|
199
|
-
// Migrations: Add columns if they don't exist
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
db.run(`ALTER TABLE work_items ADD COLUMN
|
|
206
|
-
db.run(`ALTER TABLE work_items ADD COLUMN
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
db.run(`ALTER TABLE work_items ADD COLUMN
|
|
213
|
-
db.run(`ALTER TABLE work_items ADD COLUMN architectural_decision TEXT`, async () => {
|
|
206
|
+
// Migrations: Add columns if they don't exist (for databases created before these columns were in base schema)
|
|
207
|
+
const handleAlterError = (columnName) => (err) => {
|
|
208
|
+
if (err && !err.message.includes('duplicate column')) {
|
|
209
|
+
console.warn(`ALTER TABLE ADD COLUMN ${columnName} failed:`, err.message);
|
|
210
|
+
}
|
|
211
|
+
};
|
|
212
|
+
db.run(`ALTER TABLE work_items ADD COLUMN branch_name TEXT`, handleAlterError('branch_name'));
|
|
213
|
+
db.run(`ALTER TABLE work_items ADD COLUMN file_paths TEXT`, handleAlterError('file_paths'));
|
|
214
|
+
db.run(`ALTER TABLE work_items ADD COLUMN commit_sha TEXT`, handleAlterError('commit_sha'));
|
|
215
|
+
db.run(`ALTER TABLE work_items ADD COLUMN mode TEXT`, handleAlterError('mode'));
|
|
216
|
+
db.run(`ALTER TABLE work_items ADD COLUMN current INTEGER DEFAULT 0`, handleAlterError('current'));
|
|
217
|
+
db.run(`ALTER TABLE work_items ADD COLUMN needs_discovery INTEGER DEFAULT 0`, handleAlterError('needs_discovery'));
|
|
218
|
+
db.run(`ALTER TABLE work_items ADD COLUMN worktree_path TEXT`, handleAlterError('worktree_path'));
|
|
219
|
+
db.run(`ALTER TABLE work_items ADD COLUMN discovery_rationale TEXT`, handleAlterError('discovery_rationale'));
|
|
220
|
+
db.run(`ALTER TABLE work_items ADD COLUMN architectural_decision TEXT`, async (err) => {
|
|
221
|
+
handleAlterError('architectural_decision')(err);
|
|
214
222
|
// Run data migrations after all schema operations complete (skip in test environments)
|
|
215
223
|
if (process.env.NODE_ENV !== 'test') {
|
|
216
224
|
try {
|
|
217
225
|
await runMigrations(db);
|
|
218
226
|
resolve();
|
|
219
227
|
} catch (err) {
|
|
220
|
-
|
|
221
|
-
|
|
228
|
+
console.error('Migration failed:', err.message);
|
|
229
|
+
reject(err);
|
|
222
230
|
}
|
|
223
231
|
} else {
|
|
224
232
|
resolve();
|
|
@@ -228,6 +236,38 @@ function initSchema() {
|
|
|
228
236
|
});
|
|
229
237
|
}
|
|
230
238
|
|
|
239
|
+
/**
|
|
240
|
+
* Validate that all expected columns exist in work_items table
|
|
241
|
+
* @param {sqlite3.Database} database - Database connection to validate
|
|
242
|
+
* @returns {Promise<void>} Resolves if valid, rejects with missing columns
|
|
243
|
+
*/
|
|
244
|
+
function validateSchema(database) {
|
|
245
|
+
const expectedColumns = [
|
|
246
|
+
'id', 'type', 'title', 'description', 'status', 'parent_id', 'epic_id',
|
|
247
|
+
'branch_name', 'file_paths', 'commit_sha', 'mode', 'current', 'phase',
|
|
248
|
+
'prototype_files', 'discovery_winner', 'discovery_rationale', 'scenario_file',
|
|
249
|
+
'completed_at', 'created_at', 'needs_discovery', 'worktree_path', 'architectural_decision'
|
|
250
|
+
];
|
|
251
|
+
|
|
252
|
+
return new Promise((resolve, reject) => {
|
|
253
|
+
database.all(`PRAGMA table_info(work_items)`, [], (err, rows) => {
|
|
254
|
+
if (err) {
|
|
255
|
+
return reject(new Error(`Failed to get schema info: ${err.message}`));
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const existingColumns = rows.map(r => r.name);
|
|
259
|
+
const missingColumns = expectedColumns.filter(col => !existingColumns.includes(col));
|
|
260
|
+
|
|
261
|
+
if (missingColumns.length > 0) {
|
|
262
|
+
console.error('Missing columns in work_items table:', missingColumns.join(', '));
|
|
263
|
+
return reject(new Error(`Schema validation failed: missing columns: ${missingColumns.join(', ')}`));
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
resolve();
|
|
267
|
+
});
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
|
|
231
271
|
/**
|
|
232
272
|
* Close database connection
|
|
233
273
|
* @returns {Promise<void>} Resolves when database is closed
|
|
@@ -292,6 +332,7 @@ module.exports = {
|
|
|
292
332
|
getDbPath,
|
|
293
333
|
getJettypodDir,
|
|
294
334
|
waitForMigrations,
|
|
335
|
+
validateSchema,
|
|
295
336
|
dbPath, // Deprecated: use getDbPath() for dynamic path
|
|
296
337
|
jettypodDir // Deprecated: use getJettypodDir() for dynamic path
|
|
297
338
|
};
|
package/lib/merge-lock.js
CHANGED
|
@@ -115,18 +115,30 @@ function insertLock(db, workItemId, lockedBy) {
|
|
|
115
115
|
* Clean up stale locks that are older than the threshold
|
|
116
116
|
*
|
|
117
117
|
* @param {Object} db - SQLite database connection
|
|
118
|
-
* @param {number}
|
|
119
|
-
* @returns {Promise<
|
|
118
|
+
* @param {number|Object} staleThresholdOrOptions - Age threshold in milliseconds (default: 120000), or options object with staleThreshold
|
|
119
|
+
* @returns {Promise<number>} Count of locks removed
|
|
120
120
|
*/
|
|
121
|
-
function cleanupStaleLocks(db,
|
|
121
|
+
function cleanupStaleLocks(db, staleThresholdOrOptions = 120000) {
|
|
122
|
+
if (!db) {
|
|
123
|
+
return Promise.reject(new Error('Database connection required'));
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Support both number (old API) and options object (new API)
|
|
127
|
+
let staleThresholdMs;
|
|
128
|
+
if (typeof staleThresholdOrOptions === 'object') {
|
|
129
|
+
staleThresholdMs = staleThresholdOrOptions.staleThreshold || 120000;
|
|
130
|
+
} else {
|
|
131
|
+
staleThresholdMs = staleThresholdOrOptions;
|
|
132
|
+
}
|
|
133
|
+
|
|
122
134
|
return new Promise((resolve, reject) => {
|
|
123
135
|
db.run(
|
|
124
136
|
`DELETE FROM merge_locks
|
|
125
137
|
WHERE (julianday('now') - julianday(locked_at)) * 86400000 > ?`,
|
|
126
138
|
[staleThresholdMs],
|
|
127
|
-
(err)
|
|
139
|
+
function(err) {
|
|
128
140
|
if (err) return reject(err);
|
|
129
|
-
resolve();
|
|
141
|
+
resolve(this.changes); // Return count of deleted locks
|
|
130
142
|
}
|
|
131
143
|
);
|
|
132
144
|
});
|
|
@@ -179,33 +191,6 @@ function createLockHandle(db, lockId, lockedBy, workItemId) {
|
|
|
179
191
|
};
|
|
180
192
|
}
|
|
181
193
|
|
|
182
|
-
/**
|
|
183
|
-
* Clean up stale locks older than threshold
|
|
184
|
-
*
|
|
185
|
-
* @param {Object} db - SQLite database connection
|
|
186
|
-
* @param {Object} options - Optional configuration
|
|
187
|
-
* @param {number} options.staleThreshold - Age threshold in ms (default: 300000 = 5 minutes)
|
|
188
|
-
* @returns {Promise<number>} Count of locks removed
|
|
189
|
-
*/
|
|
190
|
-
async function cleanupStaleLocks(db, options = {}) {
|
|
191
|
-
if (!db) {
|
|
192
|
-
throw new Error('Database connection required');
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
const staleThreshold = options.staleThreshold || 300000; // 5 minutes default
|
|
196
|
-
const staleTime = new Date(Date.now() - staleThreshold).toISOString();
|
|
197
|
-
|
|
198
|
-
return new Promise((resolve, reject) => {
|
|
199
|
-
db.run(
|
|
200
|
-
'DELETE FROM merge_locks WHERE locked_at < ?',
|
|
201
|
-
[staleTime],
|
|
202
|
-
function(err) {
|
|
203
|
-
if (err) reject(err);
|
|
204
|
-
else resolve(this.changes); // Return count of deleted locks
|
|
205
|
-
}
|
|
206
|
-
);
|
|
207
|
-
});
|
|
208
|
-
}
|
|
209
194
|
|
|
210
195
|
/**
|
|
211
196
|
* Sleep for specified milliseconds
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// Preflight Validator - validates prerequisites before workflow transitions
|
|
2
|
+
// Speed mode implementation - checks scenario_file and chores exist
|
|
3
|
+
|
|
4
|
+
const { getDb } = require('./database');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Validate feature prerequisites before transitioning to implementation
|
|
8
|
+
* @param {number} featureId - Feature ID to validate
|
|
9
|
+
* @returns {Promise<{success: boolean, failures: Array<{check: string, remediation: string}>}>}
|
|
10
|
+
*/
|
|
11
|
+
function validateFeatureToImplement(featureId) {
|
|
12
|
+
return new Promise((resolve, reject) => {
|
|
13
|
+
const db = getDb();
|
|
14
|
+
const failures = [];
|
|
15
|
+
|
|
16
|
+
// Get feature details
|
|
17
|
+
db.get(
|
|
18
|
+
'SELECT id, title, type, phase, scenario_file FROM work_items WHERE id = ?',
|
|
19
|
+
[featureId],
|
|
20
|
+
(err, feature) => {
|
|
21
|
+
if (err) {
|
|
22
|
+
return reject(new Error(`Failed to get feature: ${err.message}`));
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (!feature) {
|
|
26
|
+
return reject(new Error(`Feature #${featureId} not found`));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Check 1: scenario_file must be set
|
|
30
|
+
if (!feature.scenario_file) {
|
|
31
|
+
failures.push({
|
|
32
|
+
check: 'scenario_file',
|
|
33
|
+
remediation: 'Use the feature-planning skill to generate BDD scenarios first.\n Invoke skill: feature-planning'
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Check 2: chores must exist
|
|
38
|
+
db.all(
|
|
39
|
+
'SELECT id, title FROM work_items WHERE parent_id = ? AND type = ?',
|
|
40
|
+
[featureId, 'chore'],
|
|
41
|
+
(err, chores) => {
|
|
42
|
+
if (err) {
|
|
43
|
+
return reject(new Error(`Failed to get chores: ${err.message}`));
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (!chores || chores.length === 0) {
|
|
47
|
+
failures.push({
|
|
48
|
+
check: 'chores_exist',
|
|
49
|
+
remediation: 'Complete feature planning to generate speed mode chores.\n Invoke skill: feature-planning'
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
resolve({
|
|
54
|
+
success: failures.length === 0,
|
|
55
|
+
failures: failures
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
);
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
module.exports = {
|
|
65
|
+
validateFeatureToImplement
|
|
66
|
+
};
|
|
@@ -11,7 +11,7 @@ async function readStandards() {
|
|
|
11
11
|
|
|
12
12
|
// Check file exists
|
|
13
13
|
if (!fs.existsSync(standardsPath)) {
|
|
14
|
-
throw new Error('Production standards not found');
|
|
14
|
+
throw new Error('Production standards file not found');
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
// Read file with specific error handling
|
|
@@ -59,7 +59,7 @@ async function readStandards() {
|
|
|
59
59
|
function validateStandardsFormat(standards) {
|
|
60
60
|
// Validate standards object exists
|
|
61
61
|
if (!standards) {
|
|
62
|
-
throw new Error('
|
|
62
|
+
throw new Error('Standards object is null or undefined');
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
// Validate required top-level fields
|
|
@@ -98,7 +98,7 @@ function validateStandardsFormat(standards) {
|
|
|
98
98
|
}
|
|
99
99
|
|
|
100
100
|
if (!standard.acceptance) {
|
|
101
|
-
throw new Error(`
|
|
101
|
+
throw new Error(`Standard ${standard.id} missing required field: acceptance`);
|
|
102
102
|
}
|
|
103
103
|
|
|
104
104
|
// Validate infrastructure-scoped standards have all required fields
|
|
@@ -55,7 +55,7 @@ async function startWork(id) {
|
|
|
55
55
|
// Get work item
|
|
56
56
|
db.get(`
|
|
57
57
|
SELECT w.*,
|
|
58
|
-
p.title as parent_title, p.id as parent_id, p.scenario_file as parent_scenario_file, p.mode as parent_mode,
|
|
58
|
+
p.title as parent_title, p.id as parent_id, p.scenario_file as parent_scenario_file, p.mode as parent_mode, p.phase as parent_phase, p.type as parent_type,
|
|
59
59
|
e.title as epic_title, e.id as epic_id
|
|
60
60
|
FROM work_items w
|
|
61
61
|
LEFT JOIN work_items p ON w.parent_id = p.id
|
|
@@ -101,11 +101,31 @@ async function startWork(id) {
|
|
|
101
101
|
errorMsg += ` jettypod work start ${c.id} # ${c.title}\n`;
|
|
102
102
|
});
|
|
103
103
|
} else if (inProgressChores.length === 0) {
|
|
104
|
-
|
|
104
|
+
const doneChores = chores.filter(c => c.status === 'done');
|
|
105
|
+
if (doneChores.length === chores.length) {
|
|
106
|
+
errorMsg += `All chores are complete. This feature is done.\n`;
|
|
107
|
+
errorMsg += `Use: jettypod work status ${id} done # to mark the feature as done`;
|
|
108
|
+
} else {
|
|
109
|
+
errorMsg += `No chores available to start.\n`;
|
|
110
|
+
errorMsg += `Use: jettypod backlog # to see work item status`;
|
|
111
|
+
}
|
|
105
112
|
}
|
|
106
113
|
} else {
|
|
107
|
-
|
|
108
|
-
|
|
114
|
+
// No chores - check feature phase to give accurate guidance
|
|
115
|
+
if (workItem.status === 'done') {
|
|
116
|
+
errorMsg += `This feature is already done. No action needed.`;
|
|
117
|
+
} else if (workItem.phase === 'implementation') {
|
|
118
|
+
// Feature is in implementation but has no chores - unusual state
|
|
119
|
+
errorMsg += `This feature is in implementation phase but has no chores.\n`;
|
|
120
|
+
errorMsg += `The feature-planning skill should have generated chores.\n\n`;
|
|
121
|
+
errorMsg += `To regenerate chores, use the feature-planning skill:\n`;
|
|
122
|
+
errorMsg += ` Invoke skill: feature-planning`;
|
|
123
|
+
} else {
|
|
124
|
+
// Feature needs planning
|
|
125
|
+
errorMsg += `This feature needs planning first.\n\n`;
|
|
126
|
+
errorMsg += `Use the feature-planning skill to plan it:\n`;
|
|
127
|
+
errorMsg += ` Invoke skill: feature-planning`;
|
|
128
|
+
}
|
|
109
129
|
}
|
|
110
130
|
|
|
111
131
|
return reject(new Error(errorMsg));
|
|
@@ -116,7 +136,19 @@ async function startWork(id) {
|
|
|
116
136
|
|
|
117
137
|
// Prevent starting epics directly
|
|
118
138
|
if (workItem.type === 'epic') {
|
|
119
|
-
return reject(new Error(`Cannot start epic #${id} directly
|
|
139
|
+
return reject(new Error(`Cannot start epic #${id} directly.\n\nTo plan this epic's features, use the epic-planning skill:\n Invoke skill: epic-planning\n\nOr start an existing feature/chore:\n jettypod backlog # to see available work items`));
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Validate chores have their parent feature in implementation phase
|
|
143
|
+
if (workItem.type === 'chore' && workItem.parent_type === 'feature' && workItem.parent_phase !== 'implementation') {
|
|
144
|
+
return reject(new Error(
|
|
145
|
+
`Cannot start chore - feature not in implementation phase\n\n` +
|
|
146
|
+
`Chore #${id}: ${workItem.title}\n` +
|
|
147
|
+
`Parent Feature #${workItem.parent_id}: ${workItem.parent_title}\n` +
|
|
148
|
+
` Phase: ${workItem.parent_phase || 'discovery'}\n\n` +
|
|
149
|
+
`To transition the feature to implementation phase:\n` +
|
|
150
|
+
` jettypod work implement ${workItem.parent_id} --winner="..." --rationale="..."`
|
|
151
|
+
));
|
|
120
152
|
}
|
|
121
153
|
|
|
122
154
|
// Update status to in_progress if currently todo
|
|
@@ -76,8 +76,8 @@ function create(type, title, description = '', parentId = null, mode = null, nee
|
|
|
76
76
|
return continueWithValidatedParent(null);
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
-
// Check parent's mode and
|
|
80
|
-
db.get('SELECT id, title, type, mode, phase, epic_id FROM work_items WHERE id = ?', [parentId], (err, parent) => {
|
|
79
|
+
// Check parent's mode, phase, and scenario_file
|
|
80
|
+
db.get('SELECT id, title, type, mode, phase, scenario_file, epic_id FROM work_items WHERE id = ?', [parentId], (err, parent) => {
|
|
81
81
|
if (err) {
|
|
82
82
|
return reject(new Error(`Failed to validate parent: ${err.message}`));
|
|
83
83
|
}
|
|
@@ -90,19 +90,19 @@ function create(type, title, description = '', parentId = null, mode = null, nee
|
|
|
90
90
|
return reject(new Error(`Chores can only be created under features or epics. Parent #${parentId} is a ${parent.type}`));
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
-
// For chores under features, validate
|
|
94
|
-
//
|
|
95
|
-
|
|
93
|
+
// For chores under features, validate prerequisites exist
|
|
94
|
+
// Allow chores in discovery phase if scenario_file is set (feature-planning creates chores before work implement)
|
|
95
|
+
// Otherwise require mode to be set
|
|
96
|
+
if (parent.type === 'feature' && !parent.mode && !parent.scenario_file) {
|
|
96
97
|
return reject(new Error(
|
|
97
|
-
`Cannot create chore for feature without
|
|
98
|
+
`Cannot create chore for feature without BDD scenarios.\n\n` +
|
|
98
99
|
`Feature #${parent.id} "${parent.title}"\n` +
|
|
99
100
|
` Phase: ${parent.phase || 'not set'}\n` +
|
|
100
101
|
` Mode: ${parent.mode || 'not set'}\n` +
|
|
101
102
|
` Scenario file: ${parent.scenario_file || 'not set'}\n\n` +
|
|
102
|
-
`Chores can only be created after
|
|
103
|
-
`
|
|
104
|
-
`
|
|
105
|
-
` jettypod work implement ${parent.id} --scenario-file="features/[feature].feature" --winner="..." --rationale="..."`
|
|
103
|
+
`Chores can only be created after BDD scenarios are generated.\n\n` +
|
|
104
|
+
`Use the feature-planning skill to generate scenarios and chores:\n` +
|
|
105
|
+
` Invoke skill: feature-planning`
|
|
106
106
|
));
|
|
107
107
|
}
|
|
108
108
|
|
|
@@ -1077,16 +1077,14 @@ async function main() {
|
|
|
1077
1077
|
console.log('🎯 Plan this epic now?');
|
|
1078
1078
|
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
1079
1079
|
console.log('');
|
|
1080
|
-
console.log('
|
|
1081
|
-
console.log(
|
|
1080
|
+
console.log('Use the epic-planning skill:');
|
|
1081
|
+
console.log(' Invoke skill: epic-planning');
|
|
1082
1082
|
console.log('');
|
|
1083
|
-
console.log('
|
|
1083
|
+
console.log('The skill will guide you to:');
|
|
1084
1084
|
console.log(' • Brainstorm features for this epic');
|
|
1085
1085
|
console.log(' • Identify architectural decisions (if needed)');
|
|
1086
1086
|
console.log(' • Create features automatically');
|
|
1087
1087
|
console.log('');
|
|
1088
|
-
console.log('Or run: jettypod work epic-planning ' + newId);
|
|
1089
|
-
console.log('');
|
|
1090
1088
|
console.log('💡 You can also plan later when ready');
|
|
1091
1089
|
}
|
|
1092
1090
|
|
|
@@ -1101,10 +1099,8 @@ async function main() {
|
|
|
1101
1099
|
console.log('');
|
|
1102
1100
|
console.log('💡 Tip: Consider planning this epic first');
|
|
1103
1101
|
console.log('');
|
|
1104
|
-
console.log('
|
|
1105
|
-
console.log(
|
|
1106
|
-
console.log('');
|
|
1107
|
-
console.log(`Or run: jettypod work epic-planning ${parentId}`);
|
|
1102
|
+
console.log('Use the epic-planning skill:');
|
|
1103
|
+
console.log(' Invoke skill: epic-planning');
|
|
1108
1104
|
}
|
|
1109
1105
|
});
|
|
1110
1106
|
}
|
|
@@ -1831,16 +1827,14 @@ async function main() {
|
|
|
1831
1827
|
console.log(`Description: ${epic.description || 'Not provided'}`);
|
|
1832
1828
|
console.log('Needs Discovery: true');
|
|
1833
1829
|
console.log('');
|
|
1834
|
-
console.log('
|
|
1835
|
-
console.log(
|
|
1830
|
+
console.log('Use the epic-planning skill:');
|
|
1831
|
+
console.log(' Invoke skill: epic-planning');
|
|
1836
1832
|
console.log('');
|
|
1837
|
-
console.log('
|
|
1833
|
+
console.log('The skill will guide you through:');
|
|
1838
1834
|
console.log(' 1. Feature brainstorming');
|
|
1839
1835
|
console.log(' 2. Architectural decisions (if needed)');
|
|
1840
1836
|
console.log(' 3. Prototype validation (optional)');
|
|
1841
1837
|
console.log(' 4. Feature creation');
|
|
1842
|
-
console.log('');
|
|
1843
|
-
console.log('📋 The skill is at: .claude/skills/epic-planning/SKILL.md');
|
|
1844
1838
|
}
|
|
1845
1839
|
);
|
|
1846
1840
|
});
|
|
@@ -2012,6 +2006,9 @@ async function main() {
|
|
|
2012
2006
|
}
|
|
2013
2007
|
|
|
2014
2008
|
case 'implement': {
|
|
2009
|
+
// Ensure migrations are complete before accessing database
|
|
2010
|
+
await waitForMigrations();
|
|
2011
|
+
|
|
2015
2012
|
const featureId = parseInt(args[0]);
|
|
2016
2013
|
|
|
2017
2014
|
if (!featureId || isNaN(featureId)) {
|
|
@@ -2063,14 +2060,15 @@ async function main() {
|
|
|
2063
2060
|
|
|
2064
2061
|
// Validate that BDD scenarios exist (either from flag or database)
|
|
2065
2062
|
if (!scenarioFile) {
|
|
2066
|
-
console.error(`Error:
|
|
2063
|
+
console.error(`Error: Cannot implement - no BDD scenarios`);
|
|
2067
2064
|
console.log('');
|
|
2068
2065
|
console.log('Discovery is not complete without BDD scenarios.');
|
|
2069
2066
|
console.log('');
|
|
2070
|
-
console.log('
|
|
2071
|
-
console.log(
|
|
2067
|
+
console.log('Use the feature-planning skill to generate BDD scenarios:');
|
|
2068
|
+
console.log(' Invoke skill: feature-planning');
|
|
2072
2069
|
console.log('');
|
|
2073
|
-
console.log('Or
|
|
2070
|
+
console.log('Or provide the scenario file directly:');
|
|
2071
|
+
console.log(` jettypod work implement ${featureId} --scenario-file="features/[feature-name].feature" --winner="..." --rationale="..."`);
|
|
2074
2072
|
process.exit(1);
|
|
2075
2073
|
}
|
|
2076
2074
|
|
|
@@ -2090,79 +2088,126 @@ async function main() {
|
|
|
2090
2088
|
process.exit(1);
|
|
2091
2089
|
}
|
|
2092
2090
|
|
|
2093
|
-
//
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
// Prepare values
|
|
2099
|
-
const prototypeFilesValue = prototypes.length > 0 ? JSON.stringify(prototypes) : null;
|
|
2100
|
-
const winnerValue = winner || null;
|
|
2101
|
-
const rationaleValue = rationale || null;
|
|
2102
|
-
const scenarioFileValue = scenarioFile; // Already validated above
|
|
2103
|
-
|
|
2104
|
-
// Update query: if transitioning, set phase, mode, and scenario_file; if updating, just update decision fields
|
|
2105
|
-
let updateSql;
|
|
2106
|
-
let updateParams;
|
|
2107
|
-
|
|
2108
|
-
if (isTransition) {
|
|
2109
|
-
// When transitioning to implementation, ensure status is 'todo' (ready for work)
|
|
2110
|
-
// This prevents status from being left as NULL or in a discovery-related state
|
|
2111
|
-
updateSql = `UPDATE work_items SET phase = 'implementation', mode = 'speed', status = 'todo', scenario_file = ?, prototype_files = ?, discovery_winner = ?, discovery_rationale = ? WHERE id = ?`;
|
|
2112
|
-
updateParams = [scenarioFileValue, prototypeFilesValue, winnerValue, rationaleValue, featureId];
|
|
2113
|
-
} else {
|
|
2114
|
-
updateSql = `UPDATE work_items SET prototype_files = ?, discovery_winner = ?, discovery_rationale = ? WHERE id = ?`;
|
|
2115
|
-
updateParams = [prototypeFilesValue, winnerValue, rationaleValue, featureId];
|
|
2116
|
-
}
|
|
2117
|
-
|
|
2118
|
-
db.run(
|
|
2119
|
-
updateSql,
|
|
2120
|
-
updateParams,
|
|
2121
|
-
(err) => {
|
|
2091
|
+
// Check if chores exist - required before transitioning to implementation
|
|
2092
|
+
db.all(
|
|
2093
|
+
'SELECT id, title, status FROM work_items WHERE parent_id = ? AND type = ? ORDER BY created_at ASC',
|
|
2094
|
+
[featureId, 'chore'],
|
|
2095
|
+
(err, chores) => {
|
|
2122
2096
|
if (err) {
|
|
2123
|
-
console.error(`Error: ${err.message}`);
|
|
2097
|
+
console.error(`Error checking chores: ${err.message}`);
|
|
2124
2098
|
process.exit(1);
|
|
2125
2099
|
}
|
|
2126
2100
|
|
|
2127
|
-
|
|
2128
|
-
|
|
2101
|
+
if (!chores || chores.length === 0) {
|
|
2102
|
+
console.error(`Error: Cannot implement - no chores exist`);
|
|
2103
|
+
console.log('');
|
|
2104
|
+
console.log('Feature planning must generate speed mode chores before transitioning to implementation.');
|
|
2105
|
+
console.log('');
|
|
2106
|
+
console.log('Complete feature planning first:');
|
|
2107
|
+
console.log(' Invoke skill: feature-planning');
|
|
2108
|
+
process.exit(1);
|
|
2109
|
+
}
|
|
2110
|
+
|
|
2111
|
+
// Get first chore for auto-start
|
|
2112
|
+
const firstChore = chores[0];
|
|
2113
|
+
|
|
2114
|
+
// Determine if this is a transition or an update
|
|
2115
|
+
// Also handle the case where phase is implementation but mode is not set (partial state)
|
|
2116
|
+
const isTransition = feature.phase === 'discovery' || !feature.mode;
|
|
2117
|
+
const isUpdate = feature.phase === 'implementation' && feature.mode;
|
|
2118
|
+
|
|
2119
|
+
// Prepare values
|
|
2120
|
+
const prototypeFilesValue = prototypes.length > 0 ? JSON.stringify(prototypes) : null;
|
|
2121
|
+
const winnerValue = winner || null;
|
|
2122
|
+
const rationaleValue = rationale || null;
|
|
2123
|
+
const scenarioFileValue = scenarioFile; // Already validated above
|
|
2124
|
+
|
|
2125
|
+
// Update query: if transitioning, set phase, mode, and scenario_file; if updating, just update decision fields
|
|
2126
|
+
let updateSql;
|
|
2127
|
+
let updateParams;
|
|
2128
|
+
|
|
2129
2129
|
if (isTransition) {
|
|
2130
|
-
|
|
2130
|
+
// When transitioning to implementation, ensure status is 'todo' (ready for work)
|
|
2131
|
+
// This prevents status from being left as NULL or in a discovery-related state
|
|
2132
|
+
updateSql = `UPDATE work_items SET phase = 'implementation', mode = 'speed', status = 'todo', scenario_file = ?, prototype_files = ?, discovery_winner = ?, discovery_rationale = ? WHERE id = ?`;
|
|
2133
|
+
updateParams = [scenarioFileValue, prototypeFilesValue, winnerValue, rationaleValue, featureId];
|
|
2131
2134
|
} else {
|
|
2132
|
-
|
|
2135
|
+
updateSql = `UPDATE work_items SET prototype_files = ?, discovery_winner = ?, discovery_rationale = ? WHERE id = ?`;
|
|
2136
|
+
updateParams = [prototypeFilesValue, winnerValue, rationaleValue, featureId];
|
|
2133
2137
|
}
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2138
|
+
|
|
2139
|
+
db.run(
|
|
2140
|
+
updateSql,
|
|
2141
|
+
updateParams,
|
|
2142
|
+
(err) => {
|
|
2143
|
+
if (err) {
|
|
2144
|
+
console.error(`Error: ${err.message}`);
|
|
2145
|
+
process.exit(1);
|
|
2146
|
+
}
|
|
2147
|
+
|
|
2148
|
+
console.log('');
|
|
2149
|
+
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
2150
|
+
if (isTransition) {
|
|
2151
|
+
console.log(`✅ Feature #${featureId} transitioned to Implementation Phase`);
|
|
2152
|
+
} else {
|
|
2153
|
+
console.log(`✅ Feature #${featureId} discovery decision updated`);
|
|
2154
|
+
}
|
|
2155
|
+
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
2156
|
+
console.log('');
|
|
2157
|
+
console.log(`Title: ${feature.title}`);
|
|
2158
|
+
console.log('Phase: Implementation');
|
|
2159
|
+
console.log('Mode: Speed');
|
|
2160
|
+
console.log(`Scenarios: ${scenarioFileValue}`);
|
|
2161
|
+
if (prototypes.length > 0) {
|
|
2162
|
+
console.log(`Prototypes: ${prototypes.join(', ')}`);
|
|
2163
|
+
}
|
|
2164
|
+
if (winner) {
|
|
2165
|
+
console.log(`Winner: ${winner}`);
|
|
2166
|
+
}
|
|
2167
|
+
if (rationale) {
|
|
2168
|
+
console.log(`Rationale: ${rationale}`);
|
|
2169
|
+
}
|
|
2170
|
+
|
|
2171
|
+
// Auto-start first chore if transitioning
|
|
2172
|
+
if (isTransition && firstChore) {
|
|
2173
|
+
console.log('');
|
|
2174
|
+
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
2175
|
+
console.log('🚀 Auto-starting first chore...');
|
|
2176
|
+
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
2177
|
+
|
|
2178
|
+
// Import and call startWork
|
|
2179
|
+
const { startWork } = require('../work-commands');
|
|
2180
|
+
startWork(firstChore.id).then(() => {
|
|
2181
|
+
console.log('');
|
|
2182
|
+
console.log('✅ First chore started! Invoke skill: speed-mode');
|
|
2183
|
+
console.log('');
|
|
2184
|
+
}).catch((startErr) => {
|
|
2185
|
+
console.log('');
|
|
2186
|
+
console.log(`⚠️ Feature transitioned but failed to auto-start chore: ${startErr.message}`);
|
|
2187
|
+
console.log(` Manually start with: jettypod work start ${firstChore.id}`);
|
|
2188
|
+
console.log('');
|
|
2189
|
+
});
|
|
2190
|
+
} else {
|
|
2191
|
+
console.log('');
|
|
2192
|
+
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
2193
|
+
console.log('🚀 Speed Mode: Prove It Works');
|
|
2194
|
+
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
2195
|
+
console.log('');
|
|
2196
|
+
console.log('Build code that passes happy path scenarios.');
|
|
2197
|
+
console.log('Focus:');
|
|
2198
|
+
console.log(' • Happy path only');
|
|
2199
|
+
console.log(' • Single file when possible');
|
|
2200
|
+
console.log(' • localStorage for data');
|
|
2201
|
+
console.log(' • Basic try/catch');
|
|
2202
|
+
console.log('');
|
|
2203
|
+
console.log('⚠️ Speed Mode is a checkpoint - pass through quickly!');
|
|
2204
|
+
console.log('');
|
|
2205
|
+
console.log('Next: Elevate to Stable Mode');
|
|
2206
|
+
console.log(` jettypod work elevate ${featureId} stable`);
|
|
2207
|
+
console.log('');
|
|
2208
|
+
}
|
|
2209
|
+
}
|
|
2210
|
+
);
|
|
2166
2211
|
}
|
|
2167
2212
|
);
|
|
2168
2213
|
});
|
|
@@ -2236,17 +2281,15 @@ async function main() {
|
|
|
2236
2281
|
}
|
|
2237
2282
|
}
|
|
2238
2283
|
console.log('');
|
|
2239
|
-
console.log('
|
|
2240
|
-
console.log(
|
|
2284
|
+
console.log('Use the feature-planning skill:');
|
|
2285
|
+
console.log(' Invoke skill: feature-planning');
|
|
2241
2286
|
console.log('');
|
|
2242
|
-
console.log('
|
|
2287
|
+
console.log('The skill will guide you through:');
|
|
2243
2288
|
console.log(' 1. Suggesting 3 UX approaches');
|
|
2244
2289
|
console.log(' 2. Optional prototyping');
|
|
2245
2290
|
console.log(' 3. Choosing the winner');
|
|
2246
2291
|
console.log(' 4. Generating BDD scenarios');
|
|
2247
2292
|
console.log(' 5. Transitioning to implementation');
|
|
2248
|
-
console.log('');
|
|
2249
|
-
console.log('📋 The skill is at: .claude/skills/feature-planning/SKILL.md');
|
|
2250
2293
|
}
|
|
2251
2294
|
);
|
|
2252
2295
|
} else {
|
|
@@ -2261,17 +2304,15 @@ async function main() {
|
|
|
2261
2304
|
console.log(`Title: ${feature.title}`);
|
|
2262
2305
|
console.log(`Description: ${feature.description || 'Not provided'}`);
|
|
2263
2306
|
console.log('');
|
|
2264
|
-
console.log('
|
|
2265
|
-
console.log(
|
|
2307
|
+
console.log('Use the feature-planning skill:');
|
|
2308
|
+
console.log(' Invoke skill: feature-planning');
|
|
2266
2309
|
console.log('');
|
|
2267
|
-
console.log('
|
|
2310
|
+
console.log('The skill will guide you through:');
|
|
2268
2311
|
console.log(' 1. Suggesting 3 UX approaches');
|
|
2269
2312
|
console.log(' 2. Optional prototyping');
|
|
2270
2313
|
console.log(' 3. Choosing the winner');
|
|
2271
2314
|
console.log(' 4. Generating BDD scenarios');
|
|
2272
2315
|
console.log(' 5. Transitioning to implementation');
|
|
2273
|
-
console.log('');
|
|
2274
|
-
console.log('📋 The skill is at: .claude/skills/feature-planning/SKILL.md');
|
|
2275
2316
|
}
|
|
2276
2317
|
});
|
|
2277
2318
|
});
|