jettypod 3.0.2 → 4.0.0
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/.claude/PROTECT_SKILLS.md +2 -2
- package/.claude/skills/{epic-discover → epic-planning}/SKILL.md +57 -22
- package/.claude/skills/{feature-discover → feature-planning}/SKILL.md +38 -22
- package/.claude/skills/speed-mode/SKILL.md +79 -8
- package/.claude/skills/stable-mode/SKILL.md +83 -1
- package/SYSTEM-BEHAVIOR.md +172 -21
- package/docs/COMMAND_REFERENCE.md +26 -26
- package/docs/gap-analysis-current-vs-comprehensive-methodology.md +3 -3
- package/features/auto-generate-production-chores.feature +62 -11
- package/features/backlog-command.feature +26 -0
- package/features/claude-md-protection/steps.js +6 -4
- package/features/decisions/index.js +10 -10
- package/features/git-hooks/simple-steps.js +4 -4
- package/features/git-hooks/steps.js +7 -7
- package/features/mode-prompts/simple-steps.js +3 -3
- package/features/step_definitions/auto-generate-production-chores.steps.js +542 -114
- package/features/step_definitions/backlog-command.steps.js +37 -0
- package/features/work-commands/index.js +192 -8
- package/features/work-commands/simple-steps.js +5 -5
- package/features/work-commands/steps.js +2 -2
- package/features/work-tracking/index.js +220 -38
- package/features/work-tracking/mode-required.feature +1 -1
- package/jettypod.js +15 -8
- package/lib/migrations/009-discovery-rationale-field.js +24 -0
- package/lib/production-chore-generator.js +198 -0
- package/package.json +1 -1
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
const { Given, Then } = require('@cucumber/cucumber');
|
|
2
|
+
|
|
3
|
+
// Test data setup
|
|
4
|
+
Given('I have work items in various statuses', async function () {
|
|
5
|
+
// Speed mode will implement: Create test work items with different statuses
|
|
6
|
+
// (backlog, todo, in_progress, done, cancelled)
|
|
7
|
+
this.testWorkItems = null; // Placeholder for speed mode
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
// Output assertions
|
|
11
|
+
Then('I see only active work items \\(backlog, todo, in_progress)', function () {
|
|
12
|
+
// Speed mode will implement: Parse output and verify only active items shown
|
|
13
|
+
// Check output contains items with status: backlog, todo, in_progress
|
|
14
|
+
// Check output does NOT contain items with status: done, cancelled
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
Then('I see all work items regardless of status', function () {
|
|
18
|
+
// Speed mode will implement: Parse output and verify all items shown
|
|
19
|
+
// Check output contains items with all statuses
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
Then('I see only completed work items \\(done, cancelled)', function () {
|
|
23
|
+
// Speed mode will implement: Parse output and verify only completed items shown
|
|
24
|
+
// Check output contains items with status: done, cancelled
|
|
25
|
+
// Check output does NOT contain items with status: backlog, todo, in_progress
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
Then('I see the items in a tree hierarchy', function () {
|
|
29
|
+
// Speed mode will implement: Verify tree structure in output
|
|
30
|
+
// Check for indentation or tree symbols (├──, └──, etc.)
|
|
31
|
+
// Check parent-child relationships are preserved
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
Then('completed items are hidden', function () {
|
|
35
|
+
// Speed mode will implement: Verify no completed items in output
|
|
36
|
+
// Check output does NOT contain items with status: done, cancelled
|
|
37
|
+
});
|
|
@@ -163,7 +163,7 @@ function startWork(id) {
|
|
|
163
163
|
console.log('');
|
|
164
164
|
console.log('💬 Claude Code is ready to guide you through feature discovery.');
|
|
165
165
|
console.log('');
|
|
166
|
-
console.log('📋 The feature-
|
|
166
|
+
console.log('📋 The feature-planning skill will automatically activate.');
|
|
167
167
|
console.log('');
|
|
168
168
|
}
|
|
169
169
|
|
|
@@ -279,14 +279,21 @@ function stopWork(newStatus = null) {
|
|
|
279
279
|
return reject(new Error(`Database error: ${err.message}`));
|
|
280
280
|
}
|
|
281
281
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
282
|
+
// Check if this completes all stable mode chores for the parent feature
|
|
283
|
+
checkStableModeCompletion(db, currentWork, newStatus, (completionErr) => {
|
|
284
|
+
if (completionErr) {
|
|
285
|
+
console.warn(`Warning: ${completionErr.message}`);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
try {
|
|
289
|
+
clearCurrentWork();
|
|
290
|
+
} catch (unlinkErr) {
|
|
291
|
+
return reject(new Error(`Failed to remove current work file: ${unlinkErr.message}`));
|
|
292
|
+
}
|
|
287
293
|
|
|
288
|
-
|
|
289
|
-
|
|
294
|
+
console.log(`Stopped work on #${currentWork.id}, status set to ${newStatus}`);
|
|
295
|
+
resolve({ id: currentWork.id, status: newStatus });
|
|
296
|
+
});
|
|
290
297
|
});
|
|
291
298
|
} else {
|
|
292
299
|
try {
|
|
@@ -301,6 +308,183 @@ function stopWork(newStatus = null) {
|
|
|
301
308
|
});
|
|
302
309
|
}
|
|
303
310
|
|
|
311
|
+
/**
|
|
312
|
+
* Check if stable mode is complete for a feature and trigger production chore generation
|
|
313
|
+
* @param {Object} db - Database connection
|
|
314
|
+
* @param {Object} currentWork - Current work item that was just completed
|
|
315
|
+
* @param {string} newStatus - Status that was just set
|
|
316
|
+
* @param {Function} callback - Callback function
|
|
317
|
+
*/
|
|
318
|
+
async function checkStableModeCompletion(db, currentWork, newStatus, callback) {
|
|
319
|
+
// Only check if:
|
|
320
|
+
// 1. Current work is a chore
|
|
321
|
+
// 2. Status was just set to 'done'
|
|
322
|
+
// 3. Current work has a parent (feature)
|
|
323
|
+
if (currentWork.type !== 'chore' || newStatus !== 'done' || !currentWork.parent_id) {
|
|
324
|
+
return callback(null);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// Get parent feature details and check its mode
|
|
328
|
+
db.get(`
|
|
329
|
+
SELECT id, title, mode, type
|
|
330
|
+
FROM work_items
|
|
331
|
+
WHERE id = ?
|
|
332
|
+
`, [currentWork.parent_id], async (err, parent) => {
|
|
333
|
+
if (err) {
|
|
334
|
+
return callback(new Error(`Failed to get parent feature: ${err.message}`));
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
if (!parent) {
|
|
338
|
+
return callback(new Error(`Parent feature #${currentWork.parent_id} not found`));
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// Only proceed if parent is a feature in stable mode
|
|
342
|
+
if (parent.type !== 'feature' || parent.mode !== 'stable') {
|
|
343
|
+
return callback(null);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// Check if all stable chores for this feature are done
|
|
347
|
+
db.get(`
|
|
348
|
+
SELECT COUNT(*) as incomplete_count
|
|
349
|
+
FROM work_items
|
|
350
|
+
WHERE parent_id = ?
|
|
351
|
+
AND type = 'chore'
|
|
352
|
+
AND mode = 'stable'
|
|
353
|
+
AND status != 'done'
|
|
354
|
+
`, [parent.id], async (countErr, result) => {
|
|
355
|
+
if (countErr) {
|
|
356
|
+
return callback(new Error(`Failed to check stable chore completion: ${countErr.message}`));
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// If all stable chores are done, stable mode is complete
|
|
360
|
+
if (result.incomplete_count === 0) {
|
|
361
|
+
console.log('');
|
|
362
|
+
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
363
|
+
console.log('✅ STABLE MODE COMPLETE');
|
|
364
|
+
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
365
|
+
console.log('');
|
|
366
|
+
console.log(`All stable chores for feature #${parent.id} "${parent.title}" are done.`);
|
|
367
|
+
console.log('');
|
|
368
|
+
|
|
369
|
+
// Trigger production chore generation
|
|
370
|
+
try {
|
|
371
|
+
await generateAndConfirmProductionChores(parent);
|
|
372
|
+
} catch (genErr) {
|
|
373
|
+
console.error('Warning: Production chore generation failed:', genErr.message);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
callback(null);
|
|
378
|
+
});
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* Generate production chores and get user confirmation
|
|
384
|
+
* @param {Object} feature - Feature object with id and title
|
|
385
|
+
*/
|
|
386
|
+
async function generateAndConfirmProductionChores(feature) {
|
|
387
|
+
const { analyzeImplementation, generateProductionChores } = require('../../lib/production-chore-generator');
|
|
388
|
+
const readline = require('readline');
|
|
389
|
+
|
|
390
|
+
console.log('🔍 Analyzing implementation for production gaps...');
|
|
391
|
+
console.log('');
|
|
392
|
+
|
|
393
|
+
// Analyze implementation
|
|
394
|
+
let analysisResult;
|
|
395
|
+
try {
|
|
396
|
+
analysisResult = await analyzeImplementation(feature.id);
|
|
397
|
+
} catch (analysisErr) {
|
|
398
|
+
console.error(`Failed to analyze implementation: ${analysisErr.message}`);
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// Display warning if no git commits found
|
|
403
|
+
if (analysisResult.warning) {
|
|
404
|
+
console.log(`⚠️ ${analysisResult.warning}`);
|
|
405
|
+
console.log('');
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
console.log(`✅ Analyzed ${analysisResult.filesAnalyzed.length} implementation files`);
|
|
409
|
+
console.log('');
|
|
410
|
+
|
|
411
|
+
// Generate production chore proposals
|
|
412
|
+
const proposedChores = generateProductionChores(analysisResult, feature.title);
|
|
413
|
+
|
|
414
|
+
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
415
|
+
console.log('📋 PROPOSED PRODUCTION CHORES');
|
|
416
|
+
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
417
|
+
console.log('');
|
|
418
|
+
console.log('These production chores will be created:');
|
|
419
|
+
console.log('');
|
|
420
|
+
|
|
421
|
+
proposedChores.forEach((chore, index) => {
|
|
422
|
+
console.log(`${index + 1}. ${chore.title}`);
|
|
423
|
+
console.log(` ${chore.description.split('\n')[0]}`);
|
|
424
|
+
console.log('');
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
// Get user confirmation
|
|
428
|
+
return new Promise((resolve) => {
|
|
429
|
+
const rl = readline.createInterface({
|
|
430
|
+
input: process.stdin,
|
|
431
|
+
output: process.stdout
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
rl.question('Create these production chores? (yes/skip): ', async (answer) => {
|
|
435
|
+
rl.close();
|
|
436
|
+
|
|
437
|
+
const response = answer.trim().toLowerCase();
|
|
438
|
+
|
|
439
|
+
if (response === 'yes' || response === 'y') {
|
|
440
|
+
console.log('');
|
|
441
|
+
console.log('Creating production chores...');
|
|
442
|
+
|
|
443
|
+
try {
|
|
444
|
+
await createProductionChoresAndElevate(feature, proposedChores);
|
|
445
|
+
console.log('');
|
|
446
|
+
console.log(`✅ Created ${proposedChores.length} production chores. Feature elevated to production mode.`);
|
|
447
|
+
console.log('');
|
|
448
|
+
} catch (createErr) {
|
|
449
|
+
console.error(`Failed to create production chores: ${createErr.message}`);
|
|
450
|
+
}
|
|
451
|
+
} else {
|
|
452
|
+
console.log('');
|
|
453
|
+
console.log('⏭️ Skipped production chore creation');
|
|
454
|
+
console.log(' You can generate them later by talking to Claude Code');
|
|
455
|
+
console.log('');
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
resolve();
|
|
459
|
+
});
|
|
460
|
+
});
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
/**
|
|
464
|
+
* Create production chores and elevate feature to production mode (if external)
|
|
465
|
+
* @param {Object} feature - Feature object
|
|
466
|
+
* @param {Array} proposedChores - Array of chore proposals
|
|
467
|
+
*/
|
|
468
|
+
async function createProductionChoresAndElevate(feature, proposedChores) {
|
|
469
|
+
const { create } = require('../work-tracking');
|
|
470
|
+
const projectConfig = config.read();
|
|
471
|
+
|
|
472
|
+
// Create production chores
|
|
473
|
+
for (const chore of proposedChores) {
|
|
474
|
+
await create('chore', chore.title, chore.description, feature.id, 'production', false);
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
// Always elevate feature to production mode
|
|
478
|
+
// (Feature #611 controls visibility in backlog, not mode elevation)
|
|
479
|
+
const db = getDb();
|
|
480
|
+
await new Promise((resolve, reject) => {
|
|
481
|
+
db.run('UPDATE work_items SET mode = ? WHERE id = ?', ['production', feature.id], (err) => {
|
|
482
|
+
if (err) return reject(err);
|
|
483
|
+
resolve();
|
|
484
|
+
});
|
|
485
|
+
});
|
|
486
|
+
}
|
|
487
|
+
|
|
304
488
|
// Re-export getCurrentWork from shared module for backwards compatibility
|
|
305
489
|
// (used by jettypod.js)
|
|
306
490
|
|
|
@@ -22,19 +22,19 @@ Given('I have initialized jettypod', function () {
|
|
|
22
22
|
execSync('git init', { stdio: 'pipe' });
|
|
23
23
|
execSync('git config user.email "test@test.com"', { stdio: 'pipe' });
|
|
24
24
|
execSync('git config user.name "Test"', { stdio: 'pipe' });
|
|
25
|
-
execSync(`node ${path.join(originalDir, 'jettypod.js')} init`, { stdio: 'pipe' });
|
|
25
|
+
execSync(`node ${path.join(originalDir, 'jettypod.js')} init`, { stdio: 'pipe', env: { ...process.env, NODE_ENV: 'test' } });
|
|
26
26
|
});
|
|
27
27
|
|
|
28
28
|
Given('I create an epic with id {int}', function (id) {
|
|
29
|
-
execSync(`node ${path.join(originalDir, 'jettypod.js')} work create epic "Test Epic"`, { stdio: 'pipe' });
|
|
29
|
+
execSync(`node ${path.join(originalDir, 'jettypod.js')} work create epic "Test Epic"`, { stdio: 'pipe', env: { ...process.env, NODE_ENV: 'test' } });
|
|
30
30
|
});
|
|
31
31
|
|
|
32
32
|
Given('I create a feature with id {int} under epic {int}', function (featureId, epicId) {
|
|
33
|
-
execSync(`node ${path.join(originalDir, 'jettypod.js')} work create feature "Test Feature" "" --parent=${epicId}`, { stdio: 'pipe' });
|
|
33
|
+
execSync(`node ${path.join(originalDir, 'jettypod.js')} work create feature "Test Feature" "" --parent=${epicId}`, { stdio: 'pipe', env: { ...process.env, NODE_ENV: 'test' } });
|
|
34
34
|
});
|
|
35
35
|
|
|
36
36
|
When('I start work on item {int}', function (id) {
|
|
37
|
-
execSync(`node ${path.join(originalDir, 'jettypod.js')} work start ${id}`, { stdio: 'pipe' });
|
|
37
|
+
execSync(`node ${path.join(originalDir, 'jettypod.js')} work start ${id}`, { stdio: 'pipe', env: { ...process.env, NODE_ENV: 'test' } });
|
|
38
38
|
});
|
|
39
39
|
|
|
40
40
|
Then('the current work file exists', function () {
|
|
@@ -51,7 +51,7 @@ Then('the current work contains item {int}', function (id) {
|
|
|
51
51
|
Then('item {int} status is in_progress or backlog', function (id) {
|
|
52
52
|
// Use CLI to check status to avoid database connection issues
|
|
53
53
|
try {
|
|
54
|
-
const output = execSync(`node ${path.join(originalDir, 'jettypod.js')} work show ${id}`, { encoding: 'utf-8' });
|
|
54
|
+
const output = execSync(`node ${path.join(originalDir, 'jettypod.js')} work show ${id}`, { encoding: 'utf-8', env: { ...process.env, NODE_ENV: 'test' } });
|
|
55
55
|
const statusMatch = output.match(/Status:\s+(\w+)/);
|
|
56
56
|
if (!statusMatch) {
|
|
57
57
|
throw new Error('Could not find status in output');
|
|
@@ -601,9 +601,9 @@ Given('I create a feature {string} with mode {string} and parent epic', function
|
|
|
601
601
|
}
|
|
602
602
|
});
|
|
603
603
|
|
|
604
|
-
When('I view the
|
|
604
|
+
When('I view the backlog', function() {
|
|
605
605
|
testContext.lastOutput = execSync(
|
|
606
|
-
`node ${path.join(__dirname, '../../jettypod.js')}
|
|
606
|
+
`node ${path.join(__dirname, '../../jettypod.js')} backlog`,
|
|
607
607
|
{ cwd: testDir, encoding: 'utf-8' }
|
|
608
608
|
);
|
|
609
609
|
});
|