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
|
@@ -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
|
-
```
|
|
146
|
-
|
|
147
|
-
|
|
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
|
-
**
|
|
39
|
+
**To detect context, check these conditions:**
|
|
40
40
|
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
const { getDb } = require('../../lib/database');
|
|
41
|
+
```bash
|
|
42
|
+
# Get current work and parent feature
|
|
43
|
+
jettypod work current
|
|
45
44
|
|
|
46
|
-
|
|
47
|
-
|
|
45
|
+
# Check for production scenarios in the feature file
|
|
46
|
+
# (Look for scenarios tagged with @production or containing production-related keywords)
|
|
48
47
|
|
|
49
|
-
|
|
50
|
-
|
|
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
|
-
|
|
55
|
-
|
|
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
|
-
```
|
|
104
|
-
|
|
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
|
-
|
|
118
|
-
|
|
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
|
-
```
|
|
245
|
-
|
|
246
|
-
|
|
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
|
-
|
|
267
|
-
|
|
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
|
-
|
|
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
|
-
|
|
336
|
-
|
|
337
|
-
|
|
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
|
-
|
|
348
|
-
|
|
349
|
-
|
|
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
|
-
|
|
240
|
+
Parse the chore description to find which scenario it addresses (look for "Scenario: ..." in the description).
|
|
353
241
|
|
|
354
|
-
|
|
355
|
-
let iteration = 0;
|
|
356
|
-
let scenarioPasses = false;
|
|
357
|
-
let previousResult = null;
|
|
242
|
+
**RED→GREEN iteration loop:**
|
|
358
243
|
|
|
359
|
-
|
|
360
|
-
|
|
244
|
+
1. **Establish RED baseline:**
|
|
245
|
+
```bash
|
|
246
|
+
npx cucumber-js <scenario-file> --name "<scenario-name>" --format progress
|
|
247
|
+
```
|
|
361
248
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
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
|
-
|
|
368
|
-
|
|
369
|
-
|
|
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
|
-
|
|
435
|
-
console.log('All scenarios passing with production standards enforced.');
|
|
260
|
+
Ensure all scenarios still pass (no regressions).
|
|
436
261
|
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
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
|
-
**
|
|
106
|
+
**To get current work and check for breadcrumbs:**
|
|
107
107
|
|
|
108
|
-
```
|
|
109
|
-
|
|
110
|
-
|
|
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
|
-
|
|
116
|
-
|
|
112
|
+
# Get parent feature details including scenario_file
|
|
113
|
+
jettypod work show <parent-feature-id>
|
|
117
114
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
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
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
}
|
|
120
|
+
**Check for breadcrumbs** by examining the chore's description for:
|
|
121
|
+
- "Scenario steps addressed:"
|
|
122
|
+
- "Implementation guidance:"
|
|
123
|
+
- "Verification:"
|
|
138
124
|
|
|
139
|
-
|
|
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
|
-
|
|
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
|
-
|
|
206
|
-
|
|
207
|
-
|
|
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
|
-
|
|
212
|
-
|
|
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
|
-
```
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
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
|
-
|
|
351
|
-
|
|
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
|
-
|
|
358
|
-
|
|
359
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
938
|
-
|
|
939
|
-
|
|
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
|
-
|
|
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
|
|
842
|
+
**If all speed chores are done (count = 0), EXECUTE elevation:**
|
|
951
843
|
|
|
952
|
-
```
|
|
953
|
-
|
|
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
|
-
**
|
|
179
|
+
**To get scenario information, use these commands:**
|
|
180
180
|
|
|
181
|
-
```
|
|
182
|
-
|
|
183
|
-
|
|
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
|
-
|
|
247
|
-
|
|
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
|
-
|
|
254
|
-
|
|
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
|
-
**
|
|
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
|
-
|
|
284
|
-
const choreDesc = currentWork.description.toLowerCase();
|
|
285
|
-
let targetScenario = null;
|
|
194
|
+
**Identify target scenario:**
|
|
286
195
|
|
|
287
|
-
|
|
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
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
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
|
|
232
|
+
**Find speed mode files using git:**
|
|
351
233
|
|
|
352
|
-
```
|
|
353
|
-
|
|
354
|
-
|
|
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
|
-
|
|
433
|
-
|
|
434
|
-
}
|
|
238
|
+
# Get files changed in those commits
|
|
239
|
+
git diff-tree --no-commit-id --name-only -r <commit-hash>
|
|
435
240
|
|
|
436
|
-
|
|
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
|
-
|
|
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:
|