jettypod 4.4.37 → 4.4.39

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 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
- db.run('PRAGMA synchronous = NORMAL');
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
- // Note: ALTER TABLE errors are expected if column exists, so we silently ignore them
201
- db.run(`ALTER TABLE work_items ADD COLUMN branch_name TEXT`, () => {});
202
- db.run(`ALTER TABLE work_items ADD COLUMN file_paths TEXT`, () => {});
203
- db.run(`ALTER TABLE work_items ADD COLUMN commit_sha TEXT`, () => {});
204
- db.run(`ALTER TABLE work_items ADD COLUMN mode TEXT`, () => {});
205
- db.run(`ALTER TABLE work_items ADD COLUMN current INTEGER DEFAULT 0`, () => {});
206
- db.run(`ALTER TABLE work_items ADD COLUMN needs_discovery INTEGER DEFAULT 0`, () => {});
207
- // NOTE: phase column is handled by migration 006-feature-phase-field.js
208
- // Do NOT add it here - the migration includes important data migration logic
209
- // NOTE: completed_at column is handled by migration 011-add-completed-at.js
210
- // Do NOT add it here - the migration system handles it properly
211
- // NOTE: worktree_path column is handled by migration 012-add-worktree-path.js
212
- db.run(`ALTER TABLE work_items ADD COLUMN worktree_path TEXT`, () => {});
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
- // Silently ignore migration errors but still resolve - don't block startup
221
- resolve();
228
+ console.error('Migration failed:', err.message);
229
+ reject(err);
222
230
  }
223
231
  } else {
224
232
  resolve();
@@ -228,11 +236,48 @@ 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
234
274
  */
235
- function closeDb() {
275
+ async function closeDb() {
276
+ // Wait for any pending migrations before closing
277
+ if (migrationPromise) {
278
+ try { await migrationPromise; } catch { /* ignore migration errors on close */ }
279
+ }
280
+
236
281
  return new Promise((resolve) => {
237
282
  // Guard against concurrent close attempts
238
283
  if (isClosing || !db) {
@@ -292,6 +337,7 @@ module.exports = {
292
337
  getDbPath,
293
338
  getJettypodDir,
294
339
  waitForMigrations,
340
+ validateSchema,
295
341
  dbPath, // Deprecated: use getDbPath() for dynamic path
296
342
  jettypodDir // Deprecated: use getJettypodDir() for dynamic path
297
343
  };
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} staleThresholdMs - Age threshold in milliseconds (default: 120000 = 2 minutes)
119
- * @returns {Promise<void>}
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, staleThresholdMs = 120000) {
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('Invalid standards format: Standards object is null or undefined');
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(`Invalid standard format: missing acceptance field`);
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
@@ -139,6 +139,18 @@ async function startWork(id) {
139
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
140
  }
141
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
+ ));
152
+ }
153
+
142
154
  // Update status to in_progress if currently todo
143
155
  const finalStatus = workItem.status === 'todo' ? 'in_progress' : workItem.status;
144
156
 
@@ -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 phase
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 mode exists
94
- // Chores under epics are standalone and don't require mode
95
- if (parent.type === 'feature' && !parent.mode) {
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 a mode.\n\n` +
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 the feature has transitioned to implementation\n` +
103
- `with a mode (speed/stable/production).\n\n` +
104
- `To fix this, run:\n` +
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
 
@@ -2006,6 +2006,9 @@ async function main() {
2006
2006
  }
2007
2007
 
2008
2008
  case 'implement': {
2009
+ // Ensure migrations are complete before accessing database
2010
+ await waitForMigrations();
2011
+
2009
2012
  const featureId = parseInt(args[0]);
2010
2013
 
2011
2014
  if (!featureId || isNaN(featureId)) {
@@ -2057,14 +2060,15 @@ async function main() {
2057
2060
 
2058
2061
  // Validate that BDD scenarios exist (either from flag or database)
2059
2062
  if (!scenarioFile) {
2060
- console.error(`Error: Feature #${featureId} has no BDD scenarios`);
2063
+ console.error(`Error: Cannot implement - no BDD scenarios`);
2061
2064
  console.log('');
2062
2065
  console.log('Discovery is not complete without BDD scenarios.');
2063
2066
  console.log('');
2064
- console.log('To complete discovery, provide the scenario file:');
2065
- console.log(` jettypod work implement ${featureId} --scenario-file="features/[feature-name].feature" --winner="..." --rationale="..."`);
2067
+ console.log('Use the feature-planning skill to generate BDD scenarios:');
2068
+ console.log(' Invoke skill: feature-planning');
2066
2069
  console.log('');
2067
- console.log('Or generate scenarios using the feature-planning skill first.');
2070
+ console.log('Or provide the scenario file directly:');
2071
+ console.log(` jettypod work implement ${featureId} --scenario-file="features/[feature-name].feature" --winner="..." --rationale="..."`);
2068
2072
  process.exit(1);
2069
2073
  }
2070
2074
 
@@ -2084,79 +2088,126 @@ async function main() {
2084
2088
  process.exit(1);
2085
2089
  }
2086
2090
 
2087
- // Determine if this is a transition or an update
2088
- // Also handle the case where phase is implementation but mode is not set (partial state)
2089
- const isTransition = feature.phase === 'discovery' || !feature.mode;
2090
- const isUpdate = feature.phase === 'implementation' && feature.mode;
2091
-
2092
- // Prepare values
2093
- const prototypeFilesValue = prototypes.length > 0 ? JSON.stringify(prototypes) : null;
2094
- const winnerValue = winner || null;
2095
- const rationaleValue = rationale || null;
2096
- const scenarioFileValue = scenarioFile; // Already validated above
2097
-
2098
- // Update query: if transitioning, set phase, mode, and scenario_file; if updating, just update decision fields
2099
- let updateSql;
2100
- let updateParams;
2101
-
2102
- if (isTransition) {
2103
- // When transitioning to implementation, ensure status is 'todo' (ready for work)
2104
- // This prevents status from being left as NULL or in a discovery-related state
2105
- updateSql = `UPDATE work_items SET phase = 'implementation', mode = 'speed', status = 'todo', scenario_file = ?, prototype_files = ?, discovery_winner = ?, discovery_rationale = ? WHERE id = ?`;
2106
- updateParams = [scenarioFileValue, prototypeFilesValue, winnerValue, rationaleValue, featureId];
2107
- } else {
2108
- updateSql = `UPDATE work_items SET prototype_files = ?, discovery_winner = ?, discovery_rationale = ? WHERE id = ?`;
2109
- updateParams = [prototypeFilesValue, winnerValue, rationaleValue, featureId];
2110
- }
2111
-
2112
- db.run(
2113
- updateSql,
2114
- updateParams,
2115
- (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) => {
2116
2096
  if (err) {
2117
- console.error(`Error: ${err.message}`);
2097
+ console.error(`Error checking chores: ${err.message}`);
2118
2098
  process.exit(1);
2119
2099
  }
2120
2100
 
2121
- console.log('');
2122
- console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
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
+
2123
2129
  if (isTransition) {
2124
- console.log(`✅ Feature #${featureId} transitioned to Implementation Phase`);
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];
2125
2134
  } else {
2126
- console.log(`✅ Feature #${featureId} discovery decision updated`);
2127
- }
2128
- console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
2129
- console.log('');
2130
- console.log(`Title: ${feature.title}`);
2131
- console.log('Phase: Implementation');
2132
- console.log('Mode: Speed');
2133
- console.log(`Scenarios: ${scenarioFileValue}`);
2134
- if (prototypes.length > 0) {
2135
- console.log(`Prototypes: ${prototypes.join(', ')}`);
2135
+ updateSql = `UPDATE work_items SET prototype_files = ?, discovery_winner = ?, discovery_rationale = ? WHERE id = ?`;
2136
+ updateParams = [prototypeFilesValue, winnerValue, rationaleValue, featureId];
2136
2137
  }
2137
- if (winner) {
2138
- console.log(`Winner: ${winner}`);
2139
- }
2140
- if (rationale) {
2141
- console.log(`Rationale: ${rationale}`);
2142
- }
2143
- console.log('');
2144
- console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
2145
- console.log('🚀 Speed Mode: Prove It Works');
2146
- console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
2147
- console.log('');
2148
- console.log('Build code that passes happy path scenarios.');
2149
- console.log('Focus:');
2150
- console.log(' • Happy path only');
2151
- console.log(' • Single file when possible');
2152
- console.log(' • localStorage for data');
2153
- console.log(' • Basic try/catch');
2154
- console.log('');
2155
- console.log('⚠️ Speed Mode is a checkpoint - pass through quickly!');
2156
- console.log('');
2157
- console.log('Next: Elevate to Stable Mode');
2158
- console.log(` jettypod work elevate ${featureId} stable`);
2159
- console.log('');
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
+ );
2160
2211
  }
2161
2212
  );
2162
2213
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jettypod",
3
- "version": "4.4.37",
3
+ "version": "4.4.39",
4
4
  "description": "AI-powered development workflow manager with TDD, BDD, and automatic test generation",
5
5
  "main": "jettypod.js",
6
6
  "bin": {