jettypod 4.2.1 → 4.2.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jettypod",
3
- "version": "4.2.1",
3
+ "version": "4.2.2",
4
4
  "description": "AI-powered development workflow manager with TDD, BDD, and automatic test generation",
5
5
  "main": "jettypod.js",
6
6
  "bin": {
@@ -142,18 +142,9 @@ Based on chosen approach, generate:
142
142
  - All Then steps (verify outcomes)
143
143
 
144
144
  3. **Update database** with scenario file path:
145
- ```javascript
146
- // After creating scenario file AND step definitions
147
- const { getDb } = require('../../lib/database');
148
- const db = getDb();
149
-
150
- db.run(
151
- `UPDATE work_items SET scenario_file = ? WHERE id = ?`,
152
- ['features/[feature-slug].feature', featureId],
153
- (err) => {
154
- if (err) console.error('Failed to update scenario_file:', err);
155
- }
156
- );
145
+ ```bash
146
+ # After creating scenario file AND step definitions
147
+ sqlite3 .jettypod/work.db "UPDATE work_items SET scenario_file = 'features/[feature-slug].feature' WHERE id = <feature-id>"
157
148
  ```
158
149
 
159
150
  **Template for speed mode (happy path only):**
@@ -36,45 +36,24 @@ When this skill is activated, you are helping implement a production mode chore
36
36
 
37
37
  **MUST RUN FIRST:** Detect which scenario applies to this feature.
38
38
 
39
- **Code to detect context:**
39
+ **To detect context, check these conditions:**
40
40
 
41
- ```javascript
42
- const { detectContext } = require('../../lib/production-context-detector');
43
- const { getCurrentWork } = require('../../lib/current-work');
44
- const { getDb } = require('../../lib/database');
41
+ ```bash
42
+ # Get current work and parent feature
43
+ jettypod work current
45
44
 
46
- try {
47
- const currentWork = getCurrentWork();
45
+ # Check for production scenarios in the feature file
46
+ # (Look for scenarios tagged with @production or containing production-related keywords)
48
47
 
49
- if (!currentWork) {
50
- console.error('❌ No current work found. Run: jettypod work start <chore-id>');
51
- return;
52
- }
48
+ # Check for production chores
49
+ sqlite3 .jettypod/work.db "SELECT COUNT(*) FROM work_items WHERE parent_id = <feature-id> AND type = 'chore' AND mode = 'production'"
53
50
 
54
- if (!currentWork.parent_id) {
55
- console.error('❌ Current work has no parent feature.');
56
- return;
57
- }
58
-
59
- const featureId = currentWork.parent_id;
60
- const scenario = await detectContext(featureId);
61
-
62
- console.log(`📋 Detected: ${scenario}`);
63
-
64
- // Proceed with appropriate workflow
65
- if (scenario === 'SCENARIO_A') {
66
- // Fresh from stable mode - quick validation
67
- } else if (scenario === 'SCENARIO_B') {
68
- // Gap in time - re-validation
69
- } else {
70
- // Scenario C - generate from standards
71
- }
72
- } catch (err) {
73
- console.error('❌ Context detection failed:', err.message);
74
- return;
75
- }
51
+ # Check when stable mode was completed (for time gap detection)
52
+ sqlite3 .jettypod/work.db "SELECT completed_at FROM work_items WHERE parent_id = <feature-id> AND type = 'chore' AND mode = 'stable' AND status = 'done' ORDER BY completed_at DESC LIMIT 1"
76
53
  ```
77
54
 
55
+ Based on these checks, determine which scenario applies.
56
+
78
57
  **Three Scenarios:**
79
58
 
80
59
  **Scenario A: Fresh from Stable (Hot Context)**
@@ -100,29 +79,16 @@ try {
100
79
 
101
80
  **For all scenarios:** Read production standards file.
102
81
 
103
- ```javascript
104
- const { readStandards, standardsFileExists } = require('../../lib/production-standards-reader');
105
-
106
- try {
107
- if (!standardsFileExists()) {
108
- console.error('❌ Production standards file not found.');
109
- console.log('Run external-transition first to generate production standards.');
110
- return;
111
- }
112
-
113
- const standards = await readStandards();
114
- console.log(`📋 Loaded ${standards.standards.length} production standards`);
115
- console.log(`Preset: ${standards.preset}`);
82
+ ```bash
83
+ # Check if production standards file exists
84
+ ls .jettypod/production-standards.json
116
85
 
117
- // Show domains
118
- const domains = await getDomains();
119
- console.log(`Domains: ${domains.join(', ')}`);
120
- } catch (err) {
121
- console.error('❌ Failed to read production standards:', err.message);
122
- return;
123
- }
86
+ # Read the production standards
87
+ cat .jettypod/production-standards.json
124
88
  ```
125
89
 
90
+ If the file doesn't exist, run external-transition first to generate production standards.
91
+
126
92
  ---
127
93
 
128
94
  ### Step 2A: Validate Scenarios (Scenario A - Hot Context)
@@ -241,204 +207,63 @@ try {
241
207
 
242
208
  **For Scenario C only:** Generate production scenarios from standards.
243
209
 
244
- ```javascript
245
- const { generateScenariosFromStandards } = require('../../lib/production-scenario-generator');
246
- const { appendScenarios } = require('../../lib/production-scenario-appender');
247
- const { getDb } = require('../../lib/database');
248
-
249
- try {
250
- console.log('✨ Generating production scenarios from standards...');
251
-
252
- // Get feature
253
- const db = getDb();
254
- const feature = await new Promise((resolve, reject) => {
255
- db.get('SELECT * FROM work_items WHERE id = ?', [featureId], (err, row) => {
256
- if (err) reject(err);
257
- resolve(row);
258
- });
259
- });
260
-
261
- if (!feature.scenario_file) {
262
- console.error('❌ Feature has no scenario file.');
263
- return;
264
- }
210
+ ```bash
211
+ # Get feature details
212
+ sqlite3 .jettypod/work.db "SELECT id, title, scenario_file FROM work_items WHERE id = <feature-id>"
265
213
 
266
- // Generate scenarios
267
- const scenarios = await generateScenariosFromStandards(standards);
268
-
269
- console.log(`✅ Generated ${scenarios.length} production scenarios`);
270
- console.log('Domains covered:', [...new Set(standards.standards.map(s => s.domain))].join(', '));
271
-
272
- // Append to feature file
273
- await appendScenarios(feature.scenario_file, scenarios);
274
- console.log(`✅ Appended scenarios to ${feature.scenario_file}`);
275
-
276
- // Create production chores if none exist
277
- const { generateChoresFromStandards } = require('../../lib/production-chore-generator');
278
- const chores = generateChoresFromStandards(standards, feature.title);
279
-
280
- console.log(`\n📋 Proposed ${chores.length} production chores:`);
281
- chores.forEach((chore, i) => {
282
- console.log(`\n${i + 1}. ${chore.title}`);
283
- console.log(` ${chore.description.split('\n')[0]}`);
284
- });
285
-
286
- console.log('\n✅ Production scenarios ready. Create chores to implement them.');
287
- } catch (err) {
288
- console.error('❌ Scenario generation failed:', err.message);
289
- return;
290
- }
214
+ # Use jettypod to generate production scenarios
215
+ jettypod work elevate <feature-id> production
291
216
  ```
292
217
 
218
+ This will:
219
+ 1. Read production standards from `.jettypod/production-standards.json`
220
+ 2. Generate production scenarios based on applicable standards
221
+ 3. Append scenarios to the feature file
222
+ 4. Create production chores
223
+
293
224
  ---
294
225
 
295
226
  ### Step 3: Implement Production Chore
296
227
 
297
228
  **For all scenarios:** Implement the current production chore with test-driven iteration.
298
229
 
299
- ```javascript
300
- const { getCurrentWork } = require('../../lib/current-work');
301
- const { getDb } = require('../../lib/database');
302
- const {
303
- MAX_ITERATIONS,
304
- TEST_TIMEOUT,
305
- runBddTestWithTimeout,
306
- runBddScenarioWithTimeout,
307
- getScenarioLineByName,
308
- parseTestProgress,
309
- extractErrors,
310
- findNewlyPassingSteps
311
- } = require('../../.claude/skills/speed-mode/test-runner');
312
- const fs = require('fs');
313
- const path = require('path');
314
-
315
- try {
316
- const currentWork = getCurrentWork();
317
-
318
- console.log(`\n🚀 Implementing: ${currentWork.title}`);
319
- console.log(`Description: ${currentWork.description}`);
320
-
321
- // Get parent feature and scenario file
322
- const db = getDb();
323
- const feature = await new Promise((resolve, reject) => {
324
- db.get('SELECT * FROM work_items WHERE id = ?', [currentWork.parent_id], (err, row) => {
325
- if (err) reject(err);
326
- resolve(row);
327
- });
328
- });
329
-
330
- if (!feature.scenario_file) {
331
- console.error('❌ Feature has no scenario file.');
332
- return;
333
- }
230
+ **Get current work and identify target scenario:**
334
231
 
335
- // Identify which production scenario this chore addresses
336
- // Parse from chore description (e.g., "Scenario: Rate limiting on login")
337
- const scenarioMatch = currentWork.description.match(/Scenario:\s*(.+?)(?:\n|$)/i);
338
- if (!scenarioMatch) {
339
- console.error('❌ Cannot identify target scenario from chore description.');
340
- console.log('Suggestion: Update chore description to reference specific scenario.');
341
- return;
342
- }
343
-
344
- const targetScenarioName = scenarioMatch[1].trim();
345
- const scenarioLine = getScenarioLineByName(feature.scenario_file, targetScenarioName);
232
+ ```bash
233
+ # Get current chore details
234
+ jettypod work current
346
235
 
347
- if (!scenarioLine) {
348
- console.error(`❌ Cannot find scenario "${targetScenarioName}" in ${feature.scenario_file}`);
349
- return;
350
- }
236
+ # Get parent feature's scenario file
237
+ sqlite3 .jettypod/work.db "SELECT scenario_file FROM work_items WHERE id = <parent-feature-id>"
238
+ ```
351
239
 
352
- console.log(`\n📋 Target scenario: ${targetScenarioName} (line ${scenarioLine})`);
240
+ Parse the chore description to find which scenario it addresses (look for "Scenario: ..." in the description).
353
241
 
354
- // RED→GREEN iteration loop for production implementation
355
- let iteration = 0;
356
- let scenarioPasses = false;
357
- let previousResult = null;
242
+ **RED→GREEN iteration loop:**
358
243
 
359
- console.log('\n🔴 Establishing RED baseline...\n');
360
- const baselineResult = await runBddScenarioWithTimeout(feature.scenario_file, scenarioLine, TEST_TIMEOUT);
244
+ 1. **Establish RED baseline:**
245
+ ```bash
246
+ npx cucumber-js <scenario-file> --name "<scenario-name>" --format progress
247
+ ```
361
248
 
362
- if (!baselineResult.timedOut) {
363
- const baselineProgress = parseTestProgress(baselineResult.stdout + baselineResult.stderr);
364
- console.log(`RED Baseline: ${baselineProgress.total - baselineProgress.passed} of ${baselineProgress.total} steps failing\n`);
365
- }
249
+ 2. **Iterate until scenario passes (max 10 iterations):**
250
+ - Implement production hardening using Edit/Write tools
251
+ - Run the target scenario to check progress
252
+ - Parse output to track passing/failing steps
253
+ - Address first failing step each iteration
366
254
 
367
- while (!scenarioPasses && iteration < MAX_ITERATIONS) {
368
- iteration++;
369
- console.log(`\n━━━ Iteration ${iteration}/${MAX_ITERATIONS} ━━━`);
370
-
371
- // 1. Implement production hardening based on standard
372
- console.log('✍️ Implementing production hardening...');
373
- // [Claude Code uses Edit/Write tools to implement standard requirements]
374
-
375
- // 2. Run target scenario only
376
- console.log('🧪 Running target scenario...');
377
- const result = await runBddScenarioWithTimeout(feature.scenario_file, scenarioLine, TEST_TIMEOUT);
378
-
379
- if (result.timedOut) {
380
- console.error('❌ Tests timed out');
381
- break;
382
- }
383
-
384
- const currentResult = parseTestProgress(result.stdout + result.stderr);
385
- const newlyPassing = previousResult ? findNewlyPassingSteps(previousResult, currentResult) : [];
386
-
387
- console.log(`\n📊 Progress: ${currentResult.passed}/${currentResult.total} steps passing`);
388
-
389
- if (newlyPassing.length > 0) {
390
- console.log(`\n✅ Newly passing:`);
391
- newlyPassing.forEach(step => console.log(` • ${step}`));
392
- }
393
-
394
- // Check if target scenario passes
395
- if (currentResult.passed === currentResult.total && currentResult.total > 0) {
396
- console.log('\n✅ Target scenario passing!');
397
-
398
- // Run full verification (all scenarios) once for regression detection
399
- console.log('\n🔍 Running full verification (all scenarios)...');
400
- const fullResult = await runBddTestWithTimeout(feature.scenario_file, TEST_TIMEOUT);
401
-
402
- if (fullResult.timedOut) {
403
- console.log('⚠️ Full verification timed out');
404
- } else if (fullResult.exitCode !== 0) {
405
- const fullProgress = parseTestProgress(fullResult.stdout + fullResult.stderr);
406
- console.log(`⚠️ Full verification found regressions: ${fullProgress.total - fullProgress.passed} scenarios failing`);
407
- console.log('Continuing iterations to fix regressions...');
408
- scenarioPasses = false;
409
- } else {
410
- console.log('✅ Full verification passed - all scenarios passing!');
411
- scenarioPasses = true;
412
- }
413
- } else {
414
- console.log(`\n❌ ${currentResult.total - currentResult.passed} steps still failing`);
415
-
416
- const errors = extractErrors(result.stdout + result.stderr);
417
- if (errors.errors.length > 0) {
418
- console.log('\n🔧 Next failure to address:');
419
- const firstError = errors.errors[0];
420
- console.log(` Step: ${firstError.step}`);
421
- console.log(` Error: ${firstError.message}`);
422
- }
423
- }
424
-
425
- previousResult = currentResult;
426
- }
427
-
428
- if (!scenarioPasses) {
429
- console.error(`\n❌ Maximum iterations (${MAX_ITERATIONS}) reached without passing scenario`);
430
- console.log('Review implementation against production standard acceptance criteria.');
431
- return;
432
- }
255
+ 3. **Full verification when target passes:**
256
+ ```bash
257
+ npx cucumber-js <scenario-file> --format progress
258
+ ```
433
259
 
434
- console.log('\n✅ Production chore implementation complete');
435
- console.log('All scenarios passing with production standards enforced.');
260
+ Ensure all scenarios still pass (no regressions).
436
261
 
437
- } catch (err) {
438
- console.error('❌ Implementation failed:', err.message);
439
- return;
440
- }
441
- ```
262
+ **Implementation focus for production chores:**
263
+ - Security measures per standards (rate limiting, input sanitization, etc.)
264
+ - Performance targets per standards
265
+ - Monitoring and observability
266
+ - Compliance requirements
442
267
 
443
268
  ---
444
269
 
@@ -103,46 +103,28 @@ The validation will require you to:
103
103
  4. Read the parent feature's scenario file
104
104
  5. Parse the Gherkin and identify what needs to be implemented
105
105
 
106
- **Code to check for breadcrumbs:**
106
+ **To get current work and check for breadcrumbs:**
107
107
 
108
- ```javascript
109
- // --- Setup and imports ---
110
- const { getCurrentWork } = require('../../lib/current-work');
111
- const { getDb } = require('../../lib/database');
112
- const fs = require('fs');
113
- const path = require('path');
108
+ ```bash
109
+ # Get current work item with description
110
+ jettypod work current
114
111
 
115
- (async () => {
116
- const currentWork = await getCurrentWork();
112
+ # Get parent feature details including scenario_file
113
+ jettypod work show <parent-feature-id>
117
114
 
118
- // --- Check for breadcrumbs ---
119
- const hasBreadcrumbs = currentWork.description &&
120
- currentWork.description.includes('Scenario steps addressed:') &&
121
- currentWork.description.includes('Implementation guidance:') &&
122
- currentWork.description.includes('Verification:');
123
-
124
- // --- Get parent feature from database ---
125
- const db = getDb();
126
- const feature = await new Promise((resolve, reject) => {
127
- db.get('SELECT * FROM work_items WHERE id = ?', [currentWork.parent_id], (err, row) => {
128
- if (err) reject(err);
129
- else resolve(row);
130
- });
131
- });
115
+ # Or query database directly
116
+ sqlite3 .jettypod/work.db "SELECT id, title, description, parent_id FROM work_items WHERE status = 'in_progress' AND type = 'chore'"
117
+ sqlite3 .jettypod/work.db "SELECT id, title, scenario_file FROM work_items WHERE id = <parent-feature-id>"
118
+ ```
132
119
 
133
- // --- Validate scenario file exists ---
134
- if (!feature.scenario_file) {
135
- console.error('No scenario file found for this feature');
136
- return;
137
- }
120
+ **Check for breadcrumbs** by examining the chore's description for:
121
+ - "Scenario steps addressed:"
122
+ - "Implementation guidance:"
123
+ - "Verification:"
138
124
 
139
- // --- Read scenario content ---
140
- const scenarioPath = path.join(process.cwd(), feature.scenario_file);
141
- const scenarioContent = fs.readFileSync(scenarioPath, 'utf8');
125
+ If all three sections exist, use them. Otherwise, fall back to autonomous analysis.
142
126
 
143
- // Parse and analyze...
144
- })();
145
- ```
127
+ **Then read the scenario file** using the Read tool on the path returned by `scenario_file`.
146
128
 
147
129
  **Parse Gherkin:**
148
130
  - Extract first `Scenario:` block (happy path)
@@ -202,27 +184,16 @@ Now analyzing codebase to propose implementation...
202
184
 
203
185
  **Check for architectural decisions:**
204
186
 
205
- **⚠️ IMPORTANT - USE EXACT CODE BELOW:**
206
- - Import from `./lib/decisions-helpers` (NOT `../../lib/decisions-helpers` or `./features/decisions/index.js`)
207
- - Function returns a Promise, must use `await`
208
-
209
- **Execute this EXACT code (copy/paste as-is):**
187
+ ```bash
188
+ # Get the epic ID for this feature
189
+ sqlite3 .jettypod/work.db "SELECT epic_id FROM work_items WHERE id = <feature-id>"
210
190
 
211
- ```javascript
212
- const { getDecisionsForEpic } = require('./lib/decisions-helpers');
213
- const { getDb } = require('./lib/database');
214
- const db = getDb();
215
-
216
- // Get epic from current work
217
- db.get('SELECT epic_id FROM work_items WHERE id = ?', [currentWork.parent_id], async (err, feature) => {
218
- if (feature && feature.epic_id) {
219
- const decisions = await getDecisionsForEpic(feature.epic_id);
220
- console.log('Epic decisions:', JSON.stringify(decisions, null, 2));
221
- // Decisions constrain your implementation approach
222
- }
223
- });
191
+ # Get decisions for this epic
192
+ sqlite3 .jettypod/work.db "SELECT title, decision, rationale FROM discovery_decisions WHERE work_item_id = <epic-id>"
224
193
  ```
225
194
 
195
+ Decisions constrain your implementation approach - use them to guide technology choices and patterns.
196
+
226
197
  **If breadcrumbs exist (from feature-planning):**
227
198
  - Parse "Files to create/modify" section - these are your target files
228
199
  - Parse "Patterns to follow" section - read these reference files first
@@ -322,74 +293,24 @@ If user adjusts: revise proposal and confirm again.
322
293
 
323
294
  Before writing any implementation code, run tests to establish the RED state:
324
295
 
325
- ```javascript
326
- // --- Imports ---
327
- const { runBddScenarioWithTimeout, getFirstScenarioLine, parseTestProgress, extractErrors } = require('./.claude/skills/speed-mode/test-runner');
328
- const { getCurrentWork } = require('./lib/current-work');
329
- const { getDb } = require('./lib/database');
330
-
331
- (async () => {
332
- // --- Get current work and parent feature ---
333
- const currentWork = await getCurrentWork();
334
- const db = getDb();
335
-
336
- const feature = await new Promise((resolve, reject) => {
337
- db.get('SELECT * FROM work_items WHERE id = ?', [currentWork.parent_id], (err, row) => {
338
- if (err) reject(err);
339
- else resolve(row);
340
- });
341
- });
342
-
343
- if (!feature.scenario_file) {
344
- console.error('No scenario file found for this feature');
345
- return;
346
- }
347
-
348
- console.log('🔴 Establishing RED baseline...\n');
296
+ ```bash
297
+ # Get current work and parent feature's scenario file
298
+ jettypod work current
299
+ # Note the parent_id from output
349
300
 
350
- // --- Find happy path scenario ---
351
- const happyPathLine = getFirstScenarioLine(feature.scenario_file);
352
- if (!happyPathLine) {
353
- console.error('No scenario found in feature file');
354
- return;
355
- }
301
+ sqlite3 .jettypod/work.db "SELECT scenario_file FROM work_items WHERE id = <parent-feature-id>"
302
+ # This gives you the path to the .feature file
356
303
 
357
- // --- Run tests before implementation ---
358
- const result = await runBddScenarioWithTimeout(feature.scenario_file, happyPathLine);
359
- let progress = { passed: 0, total: 0, failedSteps: [] };
360
-
361
- // --- Handle timeout ---
362
- if (result.timedOut) {
363
- console.log('⚠️ Test execution timed out - tests may be hanging');
364
- console.log('Proceeding with implementation...\n');
365
- } else {
366
- // --- Parse and display results ---
367
- progress = parseTestProgress(result.stdout + result.stderr);
368
- const errors = extractErrors(result.stdout + result.stderr);
369
-
370
- console.log(`RED Baseline: ${progress.total - progress.passed} of ${progress.total} steps failing`);
371
- console.log('\nFailing steps:');
372
- progress.failedSteps.forEach(step => console.log(` ✖ ${step}`));
373
-
374
- // --- Show first error ---
375
- if (errors.errors.length > 0) {
376
- console.log('\nFirst error:');
377
- const firstError = errors.errors[0];
378
- console.log(` Step: ${firstError.step}`);
379
- console.log(` Error: ${firstError.message}`);
380
- }
304
+ # Run BDD tests to establish RED baseline
305
+ npx cucumber-js <scenario-file-path> --format progress
306
+ ```
381
307
 
382
- console.log('\n🎯 Goal: Make all steps pass\n');
383
- }
308
+ Parse the output to identify:
309
+ - Total steps and how many are failing
310
+ - Which specific steps are failing
311
+ - The first error message
384
312
 
385
- // --- Store baseline for iteration loop ---
386
- global.redBaseline = {
387
- passed: progress.passed,
388
- total: progress.total,
389
- failedSteps: progress.failedSteps
390
- };
391
- })();
392
- ```
313
+ This establishes your RED baseline - all or most steps should be failing initially.
393
314
 
394
315
  **Display RED baseline:**
395
316
  ```
@@ -908,50 +829,20 @@ Chores created:
908
829
  │ • If session interrupted, re-query to get current count │
909
830
  └─────────────────────────────────────────────────────────────────────────┘ -->
910
831
 
911
- ```javascript
912
- // --- Imports ---
913
- const { getDb } = require('../../lib/database');
914
- const { getCurrentWork } = require('../../lib/current-work');
915
-
916
- (async () => {
917
- // --- Get current work context ---
918
- const currentWork = await getCurrentWork();
919
- const featureId = currentWork.parent_id;
920
- const db = getDb();
921
-
922
- // --- Query for incomplete speed chores ---
923
- const result = await new Promise((resolve, reject) => {
924
- db.get(`
925
- SELECT COUNT(*) as incomplete_count
926
- FROM work_items
927
- WHERE parent_id = ?
928
- AND type = 'chore'
929
- AND mode = 'speed'
930
- AND status != 'done'
931
- `, [featureId], (err, row) => {
932
- if (err) reject(err);
933
- else resolve(row);
934
- });
935
- });
832
+ **Check for incomplete speed chores:**
936
833
 
937
- // --- Handle completion or remaining chores ---
938
- if (result.incomplete_count === 0) {
939
- console.log('\n✅ All speed mode chores complete!');
940
- console.log('Auto-elevating feature to stable mode...\n');
941
- // Use Bash tool to execute elevation
942
- } else {
943
- console.log(`\n📋 ${result.incomplete_count} speed mode chores remaining`);
944
- }
834
+ ```bash
835
+ # Get current work to find parent feature ID
836
+ jettypod work current
945
837
 
946
- db.close();
947
- })();
838
+ # Count incomplete speed chores for the feature
839
+ sqlite3 .jettypod/work.db "SELECT COUNT(*) FROM work_items WHERE parent_id = <feature-id> AND type = 'chore' AND mode = 'speed' AND status != 'done'"
948
840
  ```
949
841
 
950
- **If all speed chores are done, use Bash tool to EXECUTE elevation:**
842
+ **If all speed chores are done (count = 0), EXECUTE elevation:**
951
843
 
952
- ```javascript
953
- // Use Bash tool to execute:
954
- node jettypod.js work set-mode [feature-id] stable
844
+ ```bash
845
+ jettypod work set-mode <feature-id> stable
955
846
  ```
956
847
 
957
848
  **DO NOT display this as example text. EXECUTE IT using the Bash tool.**
@@ -176,148 +176,30 @@ Scenario: [Edge case title]
176
176
  3. Identify which scenario this chore addresses
177
177
  4. Extract requirements from the scenario's Given/When/Then steps
178
178
 
179
- **Code to get scenario (with error handling):**
179
+ **To get scenario information, use these commands:**
180
180
 
181
- ```javascript
182
- // --- Imports ---
183
- const { getCurrentWork } = require('../../lib/current-work');
184
- const { getDb } = require('../../lib/database');
185
- const fs = require('fs');
186
- const path = require('path');
187
-
188
- (async () => {
189
- try {
190
- const currentWork = await getCurrentWork();
191
-
192
- // --- Validate current work ---
193
- if (!currentWork) {
194
- console.error('❌ No current work found. Run: jettypod work start <chore-id>');
195
- return;
196
- }
197
-
198
- if (!currentWork.parent_id) {
199
- console.error('❌ Current work has no parent feature. This chore must be part of a feature.');
200
- return;
201
- }
202
-
203
- // --- Get parent feature ---
204
- const db = getDb();
205
- const feature = await new Promise((resolve, reject) => {
206
- db.get('SELECT * FROM work_items WHERE id = ?', [currentWork.parent_id], (err, row) => {
207
- if (err) reject(err);
208
- else resolve(row);
209
- });
210
- });
211
-
212
- // --- Validate feature ---
213
- if (!feature) {
214
- console.error('❌ Parent feature not found in database.');
215
- db.close();
216
- return;
217
- }
218
-
219
- if (!feature.scenario_file) {
220
- console.error('❌ Feature has no scenario_file. Cannot determine what to implement.');
221
- console.log('Suggestion: Create a scenario file and update the feature.');
222
- db.close();
223
- return;
224
- }
225
-
226
- // --- Validate scenario file exists ---
227
- const scenarioPath = path.join(process.cwd(), feature.scenario_file);
228
-
229
- if (!fs.existsSync(scenarioPath)) {
230
- console.error(`❌ Scenario file not found: ${scenarioPath}`);
231
- console.log('Suggestion: Create the scenario file or update the feature.scenario_file path.');
232
- db.close();
233
- return;
234
- }
235
-
236
- // --- Read scenario content ---
237
- let scenarioContent;
238
- try {
239
- scenarioContent = fs.readFileSync(scenarioPath, 'utf8');
240
- } catch (readErr) {
241
- console.error(`❌ Cannot read scenario file: ${readErr.message}`);
242
- db.close();
243
- return;
244
- }
181
+ ```bash
182
+ # Get current work (chore) and its parent feature
183
+ jettypod work current
245
184
 
246
- // --- Validate content ---
247
- if (!scenarioContent || scenarioContent.trim().length === 0) {
248
- console.error('❌ Scenario file is empty.');
249
- db.close();
250
- return;
251
- }
185
+ # Get parent feature details including scenario_file
186
+ jettypod work show <parent-feature-id>
252
187
 
253
- // Parse all scenarios...
254
- db.close();
255
- } catch (err) {
256
- console.error('❌ Unexpected error in Step 1:', err.message);
257
- return;
258
- }
259
- })();
188
+ # Or query database directly for scenario file
189
+ sqlite3 .jettypod/work.db "SELECT id, title, scenario_file, mode FROM work_items WHERE id = <parent-feature-id>"
260
190
  ```
261
191
 
262
- **Identify target scenario (with error handling):**
263
-
264
- ```javascript
265
- // --- Parse scenarios from Gherkin ---
266
- const scenarios = [];
267
- const scenarioBlocks = scenarioContent.split(/\nScenario:/);
268
-
269
- if (scenarioBlocks.length < 2) {
270
- console.error('❌ No scenarios found in scenario file.');
271
- console.log('Suggestion: Add Gherkin scenarios to the feature file.');
272
- return;
273
- }
274
-
275
- // --- Extract scenario titles ---
276
- for (let i = 1; i < scenarioBlocks.length; i++) {
277
- const block = 'Scenario:' + scenarioBlocks[i];
278
- const titleMatch = block.match(/Scenario:\s*(.+)/);
279
- const title = titleMatch ? titleMatch[1].trim() : 'Unknown';
280
- scenarios.push({ title, content: block });
281
- }
192
+ **Then read the scenario file** using the Read tool on the path returned by `scenario_file`.
282
193
 
283
- // --- Match scenario to chore ---
284
- const choreDesc = currentWork.description.toLowerCase();
285
- let targetScenario = null;
194
+ **Identify target scenario:**
286
195
 
287
- // --- Try match by scenario number ---
288
- const scenarioNumMatch = choreDesc.match(/scenario\s+(\d+)/);
289
- if (scenarioNumMatch) {
290
- const num = parseInt(scenarioNumMatch[1]);
291
- if (num > 0 && num <= scenarios.length) {
292
- targetScenario = scenarios[num - 1];
293
- }
294
- }
196
+ After reading the scenario file content, parse it to find the scenario this chore addresses:
295
197
 
296
- // --- Try match by keywords ---
297
- if (!targetScenario) {
298
- for (const scenario of scenarios) {
299
- const scenarioLower = scenario.title.toLowerCase();
300
- if (scenarios.indexOf(scenario) === 0) continue; // Skip happy path
301
-
302
- const keywords = choreDesc.split(/\s+/).filter(w => w.length > 3);
303
- const matches = keywords.filter(k => scenarioLower.includes(k));
304
-
305
- if (matches.length > 0) {
306
- targetScenario = scenario;
307
- break;
308
- }
309
- }
310
- }
311
-
312
- // --- Handle no match ---
313
- if (!targetScenario) {
314
- console.error('❌ Cannot match chore to any scenario in feature file.');
315
- console.log('Available scenarios:');
316
- scenarios.forEach((s, i) => console.log(` ${i + 1}. ${s.title}`));
317
- console.log('\nSuggestion: Update chore description to reference a specific scenario.');
318
- return;
319
- }
320
- ```
198
+ 1. Split the file by `Scenario:` to get individual scenarios
199
+ 2. Match the chore description to a scenario by:
200
+ - Looking for scenario numbers in the chore description (e.g., "Scenario 2")
201
+ - Matching keywords from the chore description to scenario titles
202
+ 3. If no match found, list available scenarios and ask which one to implement
321
203
 
322
204
  **Display to user:**
323
205
 
@@ -347,95 +229,21 @@ Now reviewing speed mode implementation...
347
229
  3. Identify what's missing for this scenario
348
230
  4. Understand current code structure
349
231
 
350
- **Find speed mode files (with error handling):**
232
+ **Find speed mode files using git:**
351
233
 
352
- ```javascript
353
- // --- Imports and setup ---
354
- const { exec } = require('child_process');
355
- const util = require('util');
356
- const execPromise = util.promisify(exec);
357
-
358
- let speedModeFiles = [];
359
-
360
- try {
361
- // --- Search git history for feature commits ---
362
- const featureName = feature.title.toLowerCase().replace(/\s+/g, '-');
363
- const { stdout: gitLog } = await execPromise(
364
- `git log --oneline --all --grep="${featureName}" -10`
365
- );
366
-
367
- // --- Handle no commits found ---
368
- if (!gitLog || gitLog.trim().length === 0) {
369
- console.log('⚠️ No git commits found for this feature.');
370
- console.log('\n📝 Which files did speed mode create/modify for this feature?');
371
- return;
372
- }
373
-
374
- // --- Extract files from commits ---
375
- const commits = gitLog.trim().split('\n').map(line => line.split(' ')[0]);
376
-
377
- for (const commit of commits) {
378
- try {
379
- const { stdout: files } = await execPromise(`git diff-tree --no-commit-id --name-only -r ${commit}`);
380
- const fileList = files.trim().split('\n').filter(f => f.length > 0);
381
- speedModeFiles.push(...fileList);
382
- } catch (diffErr) {
383
- continue;
384
- }
385
- }
386
-
387
- // --- Remove duplicates and validate ---
388
- speedModeFiles = [...new Set(speedModeFiles)];
389
-
390
- if (speedModeFiles.length === 0) {
391
- console.error('❌ No files found in git history for this feature.');
392
- console.log('📝 Which files should be reviewed?');
393
- return;
394
- }
395
-
396
- console.log(`✅ Found ${speedModeFiles.length} files from speed mode`);
397
-
398
- } catch (gitErr) {
399
- console.error('⚠️ Git error:', gitErr.message);
400
- console.log('📝 Which files did speed mode create/modify?');
401
- return;
402
- }
403
-
404
- // --- Validate files are readable ---
405
- const readableFiles = [];
406
- const unreadableFiles = [];
407
-
408
- for (const filePath of speedModeFiles) {
409
- const fullPath = path.join(process.cwd(), filePath);
410
-
411
- if (!fs.existsSync(fullPath)) {
412
- console.log(`⚠️ File no longer exists: ${filePath}`);
413
- continue;
414
- }
415
-
416
- try {
417
- fs.accessSync(fullPath, fs.constants.R_OK);
418
- readableFiles.push(filePath);
419
- } catch (accessErr) {
420
- unreadableFiles.push(filePath);
421
- console.error(`❌ Cannot read file: ${filePath}`);
422
- }
423
- }
424
-
425
- // --- Final validation ---
426
- if (readableFiles.length === 0) {
427
- console.error('❌ No readable speed mode files found.');
428
- console.log('Suggestion: Verify speed mode was completed or start from scratch.');
429
- return;
430
- }
234
+ ```bash
235
+ # Search git history for commits related to this feature
236
+ git log --oneline --all --grep="<feature-name>" -10
431
237
 
432
- if (unreadableFiles.length > 0) {
433
- console.log(`⚠️ ${unreadableFiles.length} files cannot be read - skipping`);
434
- }
238
+ # Get files changed in those commits
239
+ git diff-tree --no-commit-id --name-only -r <commit-hash>
435
240
 
436
- speedModeFiles = readableFiles;
241
+ # Or find files by looking at the feature's chores
242
+ sqlite3 .jettypod/work.db "SELECT id, title FROM work_items WHERE parent_id = <feature-id> AND type = 'chore' AND mode = 'speed'"
437
243
  ```
438
244
 
245
+ Then use the Read tool to examine the implementation files.
246
+
439
247
  **Identify gaps:**
440
248
  - What error handling is missing?
441
249
  - What validation is not performed?
@@ -717,55 +525,23 @@ Lines: ✅ 84.90%
717
525
  └─────────────────────────────────────────────────────────────────────────┘ -->
718
526
 
719
527
  **Check completion status and project state:**
720
- ```javascript
721
- // --- Imports ---
722
- const { getDb } = require('../../lib/database');
723
- const { getCurrentWork } = require('../../lib/current-work');
724
-
725
- (async () => {
726
- // --- Get current work context ---
727
- const currentWork = await getCurrentWork();
728
- const featureId = currentWork.parent_id;
729
- const db = getDb();
730
-
731
- // --- Query for incomplete chores ---
732
- const result = await new Promise((resolve, reject) => {
733
- db.get(`
734
- SELECT COUNT(*) as incomplete_count
735
- FROM work_items
736
- WHERE parent_id = ?
737
- AND type = 'chore'
738
- AND status NOT IN ('done', 'cancelled')
739
- `, [featureId], (err, row) => {
740
- if (err) reject(err);
741
- else resolve(row);
742
- });
743
- });
744
-
745
- // --- Handle all chores complete ---
746
- if (result.incomplete_count === 0) {
747
- const config = await new Promise((resolve, reject) => {
748
- db.get('SELECT project_state FROM project_config WHERE id = 1', (err, row) => {
749
- if (err) reject(err);
750
- else resolve(row);
751
- });
752
- });
753
- const projectState = config?.project_state || 'internal';
754
-
755
- // --- Route based on project state ---
756
- if (projectState === 'internal') {
757
- console.log('Internal project - no production mode needed');
758
- } else {
759
- console.log('External project - auto-generating production chores');
760
- }
761
- } else {
762
- console.log(`\n📋 ${result.incomplete_count} stable mode chores remaining`);
763
- }
764
528
 
765
- db.close();
766
- })();
529
+ ```bash
530
+ # Get current work to find parent feature ID
531
+ jettypod work current
532
+
533
+ # Count incomplete chores for the feature
534
+ sqlite3 .jettypod/work.db "SELECT COUNT(*) FROM work_items WHERE parent_id = <feature-id> AND type = 'chore' AND status NOT IN ('done', 'cancelled')"
535
+
536
+ # Check project state
537
+ sqlite3 .jettypod/work.db "SELECT project_state FROM project_config WHERE id = 1"
767
538
  ```
768
539
 
540
+ Based on the results:
541
+ - If incomplete_count > 0: More stable chores remain
542
+ - If incomplete_count = 0 AND project_state = 'internal': Feature is complete
543
+ - If incomplete_count = 0 AND project_state = 'external': Need to generate production chores
544
+
769
545
  **CRITICAL: Check project state to determine next step:**
770
546
 
771
547
  Check if project is internal or external: