jettypod 3.0.3 → 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 +209 -35
- package/features/work-tracking/mode-required.feature +1 -1
- package/jettypod.js +15 -8
- package/lib/production-chore-generator.js +198 -0
- package/package.json +1 -1
|
@@ -142,7 +142,7 @@ function getTree(includeCompleted = false) {
|
|
|
142
142
|
|
|
143
143
|
resolve(rootItems);
|
|
144
144
|
} catch (err) {
|
|
145
|
-
reject(new Error(`Failed to build
|
|
145
|
+
reject(new Error(`Failed to build backlog: ${err.message}`));
|
|
146
146
|
}
|
|
147
147
|
});
|
|
148
148
|
});
|
|
@@ -499,7 +499,7 @@ function showItem(id) {
|
|
|
499
499
|
console.log(`\n⚠️ DISCOVERY REQUIRED: This epic needs architectural decisions`);
|
|
500
500
|
console.log(``);
|
|
501
501
|
console.log(` 💬 Talk to Claude Code: "Let's do epic discovery for #${row.id}"`);
|
|
502
|
-
console.log(` Or run: jettypod work epic-
|
|
502
|
+
console.log(` Or run: jettypod work epic-planning ${row.id}`);
|
|
503
503
|
console.log(``);
|
|
504
504
|
console.log(` Claude Code will guide you through:`);
|
|
505
505
|
console.log(` • Suggesting 3 architectural options`);
|
|
@@ -611,7 +611,7 @@ async function main() {
|
|
|
611
611
|
console.log(' • Identify architectural decisions (if needed)');
|
|
612
612
|
console.log(' • Create features automatically');
|
|
613
613
|
console.log('');
|
|
614
|
-
console.log('Or run: jettypod work epic-
|
|
614
|
+
console.log('Or run: jettypod work epic-planning ' + newId);
|
|
615
615
|
console.log('');
|
|
616
616
|
console.log('💡 You can also plan later when ready');
|
|
617
617
|
}
|
|
@@ -630,7 +630,7 @@ async function main() {
|
|
|
630
630
|
console.log('Ask Claude Code:');
|
|
631
631
|
console.log(` "Help me plan epic #${parentId}"`);
|
|
632
632
|
console.log('');
|
|
633
|
-
console.log(`Or run: jettypod work epic-
|
|
633
|
+
console.log(`Or run: jettypod work epic-planning ${parentId}`);
|
|
634
634
|
}
|
|
635
635
|
});
|
|
636
636
|
}
|
|
@@ -756,13 +756,176 @@ async function main() {
|
|
|
756
756
|
console.log('Legend: ⊕ = collapsed ⊖ = expanded');
|
|
757
757
|
console.log('');
|
|
758
758
|
console.log('Commands:');
|
|
759
|
-
console.log(' jettypod
|
|
760
|
-
console.log(' jettypod
|
|
761
|
-
console.log(' jettypod
|
|
762
|
-
console.log(' jettypod
|
|
759
|
+
console.log(' jettypod backlog --expand=1 Show details for item #1');
|
|
760
|
+
console.log(' jettypod backlog --expand=1,2,3 Show details for multiple items');
|
|
761
|
+
console.log(' jettypod backlog --expand-all Show all details');
|
|
762
|
+
console.log(' jettypod backlog Collapse all (default)');
|
|
763
763
|
console.log('');
|
|
764
764
|
} catch (err) {
|
|
765
|
-
console.error(`Error displaying
|
|
765
|
+
console.error(`Error displaying backlog: ${err.message}`);
|
|
766
|
+
process.exit(1);
|
|
767
|
+
}
|
|
768
|
+
break;
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
case 'backlog': {
|
|
772
|
+
try {
|
|
773
|
+
const filter = args[0]; // undefined, 'all', or 'completed'
|
|
774
|
+
|
|
775
|
+
// For 'all' and 'completed' filters, use old simple display
|
|
776
|
+
if (filter === 'all' || filter === 'completed') {
|
|
777
|
+
let items;
|
|
778
|
+
if (filter === 'all') {
|
|
779
|
+
items = await getTree(true);
|
|
780
|
+
} else {
|
|
781
|
+
items = await new Promise((resolve, reject) => {
|
|
782
|
+
db.all(`SELECT * FROM work_items WHERE status IN ('done', 'cancelled') ORDER BY parent_id, id`, [], (err, rows) => {
|
|
783
|
+
if (err) {
|
|
784
|
+
return reject(new Error(`Failed to fetch completed items: ${err.message}`));
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
if (!rows || rows.length === 0) {
|
|
788
|
+
return resolve([]);
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
const itemsById = {};
|
|
792
|
+
const rootItems = [];
|
|
793
|
+
|
|
794
|
+
rows.forEach(item => {
|
|
795
|
+
itemsById[item.id] = item;
|
|
796
|
+
item.children = [];
|
|
797
|
+
});
|
|
798
|
+
|
|
799
|
+
rows.forEach(item => {
|
|
800
|
+
if (item.parent_id && itemsById[item.parent_id]) {
|
|
801
|
+
itemsById[item.parent_id].children.push(item);
|
|
802
|
+
} else if (!item.parent_id) {
|
|
803
|
+
rootItems.push(item);
|
|
804
|
+
}
|
|
805
|
+
});
|
|
806
|
+
|
|
807
|
+
resolve(rootItems);
|
|
808
|
+
});
|
|
809
|
+
});
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
if (items.length === 0) {
|
|
813
|
+
console.log('No items found.');
|
|
814
|
+
} else {
|
|
815
|
+
const expandedIds = new Set();
|
|
816
|
+
items.forEach(item => {
|
|
817
|
+
if (item.type === 'epic') {
|
|
818
|
+
expandedIds.add(item.id);
|
|
819
|
+
}
|
|
820
|
+
});
|
|
821
|
+
printTree(items, '', true, expandedIds);
|
|
822
|
+
}
|
|
823
|
+
console.log('');
|
|
824
|
+
} else {
|
|
825
|
+
// Default: show three-section view (active work, recently completed, backlog)
|
|
826
|
+
const currentWork = getCurrentWork();
|
|
827
|
+
|
|
828
|
+
// Show active work at top if exists
|
|
829
|
+
if (currentWork) {
|
|
830
|
+
const emoji = TYPE_EMOJIS[currentWork.type] || '📋';
|
|
831
|
+
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
832
|
+
console.log('🎯 ACTIVE WORK');
|
|
833
|
+
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
834
|
+
console.log(`${emoji} [#${currentWork.id}] ${currentWork.title}`);
|
|
835
|
+
if (currentWork.epic_title && currentWork.epic_id !== currentWork.id && currentWork.parent_id === currentWork.epic_id) {
|
|
836
|
+
console.log(`└─ Epic: 🎯 #${currentWork.epic_id} ${currentWork.epic_title}`);
|
|
837
|
+
} else if (currentWork.parent_title) {
|
|
838
|
+
const parentEmoji = TYPE_EMOJIS[currentWork.parent_type] || '📋';
|
|
839
|
+
console.log(`└─ Part of: ${parentEmoji} #${currentWork.parent_id} ${currentWork.parent_title}`);
|
|
840
|
+
} else if (currentWork.epic_title && currentWork.epic_id !== currentWork.id) {
|
|
841
|
+
console.log(`└─ Epic: 🎯 #${currentWork.epic_id} ${currentWork.epic_title}`);
|
|
842
|
+
}
|
|
843
|
+
console.log('');
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
// Show recently completed items
|
|
847
|
+
const recentlyCompleted = await new Promise((resolve, reject) => {
|
|
848
|
+
db.all(`
|
|
849
|
+
SELECT w.id, w.title, w.type, w.mode,
|
|
850
|
+
p.title as parent_title, p.id as parent_id, p.type as parent_type
|
|
851
|
+
FROM work_items w
|
|
852
|
+
LEFT JOIN work_items p ON w.parent_id = p.id
|
|
853
|
+
WHERE w.status = 'done'
|
|
854
|
+
ORDER BY w.id DESC
|
|
855
|
+
LIMIT 3
|
|
856
|
+
`, [], async (err, rows) => {
|
|
857
|
+
if (err) reject(err);
|
|
858
|
+
else {
|
|
859
|
+
// Add epic info to each item
|
|
860
|
+
for (const row of rows || []) {
|
|
861
|
+
const epic = await findEpic(row.id);
|
|
862
|
+
if (epic) {
|
|
863
|
+
row.epic_id = epic.id;
|
|
864
|
+
row.epic_title = epic.title;
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
resolve(rows || []);
|
|
868
|
+
}
|
|
869
|
+
});
|
|
870
|
+
});
|
|
871
|
+
|
|
872
|
+
if (recentlyCompleted.length > 0) {
|
|
873
|
+
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
874
|
+
console.log('✅ RECENTLY COMPLETED');
|
|
875
|
+
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
876
|
+
recentlyCompleted.forEach(item => {
|
|
877
|
+
const emoji = TYPE_EMOJIS[item.type] || '📋';
|
|
878
|
+
let modeIndicator = '';
|
|
879
|
+
if (item.type === 'feature') {
|
|
880
|
+
if (item.phase === 'discovery') {
|
|
881
|
+
modeIndicator = ' [🔍 discovery]';
|
|
882
|
+
} else if (item.mode) {
|
|
883
|
+
modeIndicator = ` [${item.mode}]`;
|
|
884
|
+
}
|
|
885
|
+
} else if (item.mode) {
|
|
886
|
+
modeIndicator = ` [${item.mode}]`;
|
|
887
|
+
}
|
|
888
|
+
console.log(`${emoji} [${item.id}] ${item.title}${modeIndicator}`);
|
|
889
|
+
if (item.epic_title && item.epic_id !== item.id && item.parent_id === item.epic_id) {
|
|
890
|
+
console.log(` └─ Epic: 🎯 #${item.epic_id} ${item.epic_title}`);
|
|
891
|
+
} else if (item.parent_title) {
|
|
892
|
+
const parentEmoji = TYPE_EMOJIS[item.parent_type] || '📋';
|
|
893
|
+
console.log(` └─ Part of: ${parentEmoji} #${item.parent_id} ${item.parent_title}`);
|
|
894
|
+
} else if (item.epic_title && item.epic_id !== item.id) {
|
|
895
|
+
console.log(` └─ Epic: 🎯 #${item.epic_id} ${item.epic_title}`);
|
|
896
|
+
}
|
|
897
|
+
});
|
|
898
|
+
console.log('');
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
902
|
+
console.log('📋 BACKLOG');
|
|
903
|
+
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
904
|
+
const items = await getTree(false);
|
|
905
|
+
|
|
906
|
+
// Default: auto-expand all epics
|
|
907
|
+
const expandedIds = new Set();
|
|
908
|
+
items.forEach(item => {
|
|
909
|
+
if (item.type === 'epic') {
|
|
910
|
+
expandedIds.add(item.id);
|
|
911
|
+
}
|
|
912
|
+
});
|
|
913
|
+
|
|
914
|
+
printTree(items, '', true, expandedIds);
|
|
915
|
+
|
|
916
|
+
// Show legend and commands
|
|
917
|
+
console.log('');
|
|
918
|
+
console.log('Legend: ⊕ = collapsed ⊖ = expanded');
|
|
919
|
+
console.log('');
|
|
920
|
+
console.log('Commands:');
|
|
921
|
+
console.log(' jettypod backlog --expand=1 Show details for item #1');
|
|
922
|
+
console.log(' jettypod backlog --expand=1,2,3 Show details for multiple items');
|
|
923
|
+
console.log(' jettypod backlog --expand-all Show all details');
|
|
924
|
+
console.log(' jettypod backlog Collapse all (default)');
|
|
925
|
+
console.log('');
|
|
926
|
+
}
|
|
927
|
+
} catch (err) {
|
|
928
|
+
console.error(`Error displaying backlog: ${err.message}`);
|
|
766
929
|
process.exit(1);
|
|
767
930
|
}
|
|
768
931
|
break;
|
|
@@ -809,7 +972,7 @@ async function main() {
|
|
|
809
972
|
console.log('Example:');
|
|
810
973
|
console.log(' jettypod work show 5');
|
|
811
974
|
console.log('');
|
|
812
|
-
console.log('💡 Tip: Use `jettypod
|
|
975
|
+
console.log('💡 Tip: Use `jettypod backlog` to see all work items');
|
|
813
976
|
process.exit(1);
|
|
814
977
|
}
|
|
815
978
|
|
|
@@ -967,12 +1130,12 @@ async function main() {
|
|
|
967
1130
|
break;
|
|
968
1131
|
}
|
|
969
1132
|
|
|
970
|
-
case 'epic-
|
|
1133
|
+
case 'epic-planning': {
|
|
971
1134
|
const epicId = parseInt(args[0]);
|
|
972
1135
|
|
|
973
1136
|
if (!epicId || isNaN(epicId)) {
|
|
974
1137
|
console.error('Error: Epic ID is required');
|
|
975
|
-
console.log('Usage: jettypod work epic-
|
|
1138
|
+
console.log('Usage: jettypod work epic-planning <epic-id>');
|
|
976
1139
|
process.exit(1);
|
|
977
1140
|
}
|
|
978
1141
|
|
|
@@ -1038,13 +1201,13 @@ async function main() {
|
|
|
1038
1201
|
console.log('💬 Now ask Claude Code:');
|
|
1039
1202
|
console.log(` "Help me with epic discovery for #${epicId}"`);
|
|
1040
1203
|
console.log('');
|
|
1041
|
-
console.log('Claude will use the epic-
|
|
1204
|
+
console.log('Claude will use the epic-planning skill to guide you through:');
|
|
1042
1205
|
console.log(' 1. Feature brainstorming');
|
|
1043
1206
|
console.log(' 2. Architectural decisions (if needed)');
|
|
1044
1207
|
console.log(' 3. Prototype validation (optional)');
|
|
1045
1208
|
console.log(' 4. Feature creation');
|
|
1046
1209
|
console.log('');
|
|
1047
|
-
console.log('📋 The skill is at: .claude/skills/epic-
|
|
1210
|
+
console.log('📋 The skill is at: .claude/skills/epic-planning/SKILL.md');
|
|
1048
1211
|
}
|
|
1049
1212
|
);
|
|
1050
1213
|
});
|
|
@@ -1258,21 +1421,14 @@ async function main() {
|
|
|
1258
1421
|
process.exit(1);
|
|
1259
1422
|
}
|
|
1260
1423
|
|
|
1261
|
-
|
|
1262
|
-
console.error(`Error: Feature #${featureId} is not in discovery phase (current phase: ${feature.phase || 'implementation'})`);
|
|
1263
|
-
console.log('');
|
|
1264
|
-
console.log('Features can only be transitioned to implementation from discovery phase.');
|
|
1265
|
-
process.exit(1);
|
|
1266
|
-
}
|
|
1267
|
-
|
|
1268
|
-
// Validate that BDD scenarios exist before transitioning
|
|
1424
|
+
// Validate that BDD scenarios exist
|
|
1269
1425
|
if (!feature.scenario_file) {
|
|
1270
1426
|
console.error(`Error: Feature #${featureId} has no BDD scenarios`);
|
|
1271
1427
|
console.log('');
|
|
1272
1428
|
console.log('Discovery is not complete without BDD scenarios.');
|
|
1273
1429
|
console.log('');
|
|
1274
1430
|
console.log('To complete discovery:');
|
|
1275
|
-
console.log(' 1. Generate scenarios using feature-
|
|
1431
|
+
console.log(' 1. Generate scenarios using feature-planning skill');
|
|
1276
1432
|
console.log(' 2. Or manually create scenarios at features/[feature-name].feature');
|
|
1277
1433
|
console.log(' 3. Then run: jettypod work implement');
|
|
1278
1434
|
console.log('');
|
|
@@ -1298,13 +1454,29 @@ async function main() {
|
|
|
1298
1454
|
process.exit(1);
|
|
1299
1455
|
}
|
|
1300
1456
|
|
|
1301
|
-
//
|
|
1457
|
+
// Determine if this is a transition or an update
|
|
1458
|
+
const isTransition = feature.phase === 'discovery';
|
|
1459
|
+
const isUpdate = feature.phase === 'implementation';
|
|
1460
|
+
|
|
1461
|
+
if (!isTransition && !isUpdate) {
|
|
1462
|
+
console.error(`Error: Feature #${featureId} has invalid phase: ${feature.phase}`);
|
|
1463
|
+
console.log('');
|
|
1464
|
+
console.log('Expected phase to be either "discovery" or "implementation"');
|
|
1465
|
+
process.exit(1);
|
|
1466
|
+
}
|
|
1467
|
+
|
|
1468
|
+
// Prepare values
|
|
1302
1469
|
const prototypeFilesValue = prototypes.length > 0 ? JSON.stringify(prototypes) : null;
|
|
1303
1470
|
const winnerValue = winner || null;
|
|
1304
1471
|
const rationaleValue = rationale || null;
|
|
1305
1472
|
|
|
1473
|
+
// Update query: if transitioning, set phase and mode; if updating, just update decision fields
|
|
1474
|
+
const updateSql = isTransition
|
|
1475
|
+
? `UPDATE work_items SET phase = 'implementation', mode = 'speed', prototype_files = ?, discovery_winner = ?, discovery_rationale = ? WHERE id = ?`
|
|
1476
|
+
: `UPDATE work_items SET prototype_files = ?, discovery_winner = ?, discovery_rationale = ? WHERE id = ?`;
|
|
1477
|
+
|
|
1306
1478
|
db.run(
|
|
1307
|
-
|
|
1479
|
+
updateSql,
|
|
1308
1480
|
[prototypeFilesValue, winnerValue, rationaleValue, featureId],
|
|
1309
1481
|
(err) => {
|
|
1310
1482
|
if (err) {
|
|
@@ -1314,7 +1486,11 @@ async function main() {
|
|
|
1314
1486
|
|
|
1315
1487
|
console.log('');
|
|
1316
1488
|
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
1317
|
-
|
|
1489
|
+
if (isTransition) {
|
|
1490
|
+
console.log(`✅ Feature #${featureId} transitioned to Implementation Phase`);
|
|
1491
|
+
} else {
|
|
1492
|
+
console.log(`✅ Feature #${featureId} discovery decision updated`);
|
|
1493
|
+
}
|
|
1318
1494
|
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
1319
1495
|
console.log('');
|
|
1320
1496
|
console.log(`Title: ${feature.title}`);
|
|
@@ -1422,14 +1598,14 @@ async function main() {
|
|
|
1422
1598
|
console.log('💬 Now ask Claude Code:');
|
|
1423
1599
|
console.log(` "Help me with feature discovery for #${featureId}"`);
|
|
1424
1600
|
console.log('');
|
|
1425
|
-
console.log('Claude will use the feature-
|
|
1601
|
+
console.log('Claude will use the feature-planning skill to guide you through:');
|
|
1426
1602
|
console.log(' 1. Suggesting 3 UX approaches');
|
|
1427
1603
|
console.log(' 2. Optional prototyping');
|
|
1428
1604
|
console.log(' 3. Choosing the winner');
|
|
1429
1605
|
console.log(' 4. Generating BDD scenarios');
|
|
1430
1606
|
console.log(' 5. Transitioning to implementation');
|
|
1431
1607
|
console.log('');
|
|
1432
|
-
console.log('📋 The skill is at: .claude/skills/feature-
|
|
1608
|
+
console.log('📋 The skill is at: .claude/skills/feature-planning/SKILL.md');
|
|
1433
1609
|
}
|
|
1434
1610
|
);
|
|
1435
1611
|
} else {
|
|
@@ -1447,14 +1623,14 @@ async function main() {
|
|
|
1447
1623
|
console.log('💬 Now ask Claude Code:');
|
|
1448
1624
|
console.log(` "Help me with feature discovery for #${featureId}"`);
|
|
1449
1625
|
console.log('');
|
|
1450
|
-
console.log('Claude will use the feature-
|
|
1626
|
+
console.log('Claude will use the feature-planning skill to guide you through:');
|
|
1451
1627
|
console.log(' 1. Suggesting 3 UX approaches');
|
|
1452
1628
|
console.log(' 2. Optional prototyping');
|
|
1453
1629
|
console.log(' 3. Choosing the winner');
|
|
1454
1630
|
console.log(' 4. Generating BDD scenarios');
|
|
1455
1631
|
console.log(' 5. Transitioning to implementation');
|
|
1456
1632
|
console.log('');
|
|
1457
|
-
console.log('📋 The skill is at: .claude/skills/feature-
|
|
1633
|
+
console.log('📋 The skill is at: .claude/skills/feature-planning/SKILL.md');
|
|
1458
1634
|
}
|
|
1459
1635
|
});
|
|
1460
1636
|
});
|
|
@@ -1469,11 +1645,9 @@ Commands:
|
|
|
1469
1645
|
jettypod work create <type> <title> [desc] [--parent=ID]
|
|
1470
1646
|
Types: epic, feature, bug, chore
|
|
1471
1647
|
|
|
1472
|
-
jettypod
|
|
1473
|
-
Show
|
|
1474
|
-
|
|
1475
|
-
jettypod work completed
|
|
1476
|
-
Show all completed work items
|
|
1648
|
+
jettypod backlog [filter]
|
|
1649
|
+
Show work items (active by default)
|
|
1650
|
+
Filters: all, completed
|
|
1477
1651
|
|
|
1478
1652
|
jettypod work show <id>
|
|
1479
1653
|
Show work item details
|
|
@@ -82,7 +82,7 @@ Feature: Mode defaults to discovery on work item creation
|
|
|
82
82
|
And I create a feature "Speed Feature" with mode "speed" and parent epic
|
|
83
83
|
And I create a bug "Stable Bug" with mode "stable" and parent epic
|
|
84
84
|
And I create a chore "Test Chore" without mode and parent epic
|
|
85
|
-
When I view the
|
|
85
|
+
When I view the backlog
|
|
86
86
|
Then I see the epic without mode indicator
|
|
87
87
|
And I see the feature with mode "speed"
|
|
88
88
|
And I see the bug with mode "stable"
|
package/jettypod.js
CHANGED
|
@@ -138,7 +138,7 @@ Status: ${currentWork.status}
|
|
|
138
138
|
Use JettyPod commands to track work:
|
|
139
139
|
- jettypod work create <type> <title> [desc] [--parent=ID]
|
|
140
140
|
- jettypod work start <id>
|
|
141
|
-
- jettypod
|
|
141
|
+
- jettypod backlog
|
|
142
142
|
- jettypod decisions (view architectural and technical decisions)
|
|
143
143
|
|
|
144
144
|
EPIC DISCOVERY DETECTION:
|
|
@@ -194,7 +194,7 @@ CONFIRMATION REQUIRED BEFORE TRANSITION:
|
|
|
194
194
|
When user expresses intent to: prepare for production, launch to customers, accept external users, go live, deploy publicly, make it production-ready, or similar - you MUST:
|
|
195
195
|
|
|
196
196
|
1. **STOP - First, check what will be created**:
|
|
197
|
-
- Run \`jettypod
|
|
197
|
+
- Run \`jettypod backlog\` to count stable mode features
|
|
198
198
|
- Each stable feature will get 3 production chores (Security, Scale, Compliance)
|
|
199
199
|
|
|
200
200
|
2. **Ask for confirmation with specific impact**:
|
|
@@ -476,7 +476,7 @@ This splits your terminal so you can see both at once:
|
|
|
476
476
|
│ terminal │ terminal │
|
|
477
477
|
└────────────┴────────────┘
|
|
478
478
|
|
|
479
|
-
In the right terminal, type \`jettypod
|
|
479
|
+
In the right terminal, type \`jettypod backlog\` and press Enter - you'll see a visual tree of all your work."
|
|
480
480
|
|
|
481
481
|
**CRITICAL: After creating all epics, save checkpoint by running:**
|
|
482
482
|
try {
|
|
@@ -577,8 +577,8 @@ I'll help you:
|
|
|
577
577
|
|
|
578
578
|
**Which epic should we start with?**"
|
|
579
579
|
|
|
580
|
-
**CRITICAL: After user picks an epic, immediately invoke the epic-
|
|
581
|
-
Use the Skill tool to invoke: epic-
|
|
580
|
+
**CRITICAL: After user picks an epic, immediately invoke the epic-planning skill:**
|
|
581
|
+
Use the Skill tool to invoke: epic-planning
|
|
582
582
|
|
|
583
583
|
This ensures consistent epic planning workflow. DO NOT manually guide them - let the skill handle it.
|
|
584
584
|
|
|
@@ -1053,6 +1053,13 @@ switch (command) {
|
|
|
1053
1053
|
}
|
|
1054
1054
|
break;
|
|
1055
1055
|
|
|
1056
|
+
case 'backlog':
|
|
1057
|
+
// Backlog viewing - delegates to work tracking module
|
|
1058
|
+
const workTracking = require('./features/work-tracking/index.js');
|
|
1059
|
+
process.argv = ['node', 'work-tracking', 'backlog', ...args];
|
|
1060
|
+
workTracking.main();
|
|
1061
|
+
break;
|
|
1062
|
+
|
|
1056
1063
|
case 'docs':
|
|
1057
1064
|
const docsSubcommand = args[0];
|
|
1058
1065
|
|
|
@@ -1340,7 +1347,7 @@ switch (command) {
|
|
|
1340
1347
|
}
|
|
1341
1348
|
|
|
1342
1349
|
console.log(`✅ Created ${choresCreated} chores for external readiness`);
|
|
1343
|
-
console.log(`\nRun 'jettypod
|
|
1350
|
+
console.log(`\nRun 'jettypod backlog' to see all items`);
|
|
1344
1351
|
console.log(`Run 'jettypod work start <id>' to begin work`);
|
|
1345
1352
|
|
|
1346
1353
|
} catch (err) {
|
|
@@ -1508,7 +1515,7 @@ switch (command) {
|
|
|
1508
1515
|
console.log(' jettypod work create epic "Your First Epic" "Description"');
|
|
1509
1516
|
console.log('');
|
|
1510
1517
|
console.log('Then plan the features for that epic:');
|
|
1511
|
-
console.log(' jettypod work epic-
|
|
1518
|
+
console.log(' jettypod work epic-planning <epic-id>');
|
|
1512
1519
|
console.log('');
|
|
1513
1520
|
console.log('💡 Tip: Claude Code will help you brainstorm features for each epic');
|
|
1514
1521
|
|
|
@@ -1576,7 +1583,7 @@ CLAUDE.md has been generated with your current project context.
|
|
|
1576
1583
|
Open Claude Code to start working.
|
|
1577
1584
|
|
|
1578
1585
|
Quick commands:
|
|
1579
|
-
jettypod
|
|
1586
|
+
jettypod backlog Show all work items
|
|
1580
1587
|
jettypod project info Show project status
|
|
1581
1588
|
`);
|
|
1582
1589
|
}
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
// Production chore generator - analyzes implementation and proposes production-ready chores
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const { getDb } = require('./database');
|
|
5
|
+
const { execSync } = require('child_process');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Analyze implementation files for a feature to identify production gaps
|
|
9
|
+
* @param {number} featureId - Feature ID to analyze
|
|
10
|
+
* @returns {Promise<Object>} Analysis result with files and production gaps
|
|
11
|
+
*/
|
|
12
|
+
async function analyzeImplementation(featureId) {
|
|
13
|
+
const db = getDb();
|
|
14
|
+
|
|
15
|
+
// Get feature details
|
|
16
|
+
const feature = await new Promise((resolve, reject) => {
|
|
17
|
+
db.get('SELECT * FROM work_items WHERE id = ?', [featureId], (err, row) => {
|
|
18
|
+
if (err) return reject(err);
|
|
19
|
+
resolve(row);
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
if (!feature) {
|
|
24
|
+
throw new Error(`Feature #${featureId} not found`);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Get files modified for this feature from git history
|
|
28
|
+
let implementationFiles = [];
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
const featureSlug = feature.title.toLowerCase().replace(/\s+/g, '-');
|
|
32
|
+
|
|
33
|
+
// Try to find commits for this feature
|
|
34
|
+
const gitLog = execSync(
|
|
35
|
+
`git log --oneline --all --grep="${featureSlug}" -10`,
|
|
36
|
+
{ encoding: 'utf8', stdio: ['pipe', 'pipe', 'ignore'] }
|
|
37
|
+
).trim();
|
|
38
|
+
|
|
39
|
+
if (gitLog) {
|
|
40
|
+
const commits = gitLog.split('\n').map(line => line.split(' ')[0]);
|
|
41
|
+
|
|
42
|
+
for (const commit of commits) {
|
|
43
|
+
try {
|
|
44
|
+
const files = execSync(
|
|
45
|
+
`git diff-tree --no-commit-id --name-only -r ${commit}`,
|
|
46
|
+
{ encoding: 'utf8', stdio: ['pipe', 'pipe', 'ignore'] }
|
|
47
|
+
).trim();
|
|
48
|
+
|
|
49
|
+
if (files) {
|
|
50
|
+
const fileList = files.split('\n').filter(f =>
|
|
51
|
+
f.length > 0 &&
|
|
52
|
+
!f.includes('test') &&
|
|
53
|
+
!f.includes('.feature') &&
|
|
54
|
+
(f.endsWith('.js') || f.endsWith('.ts') || f.endsWith('.jsx') || f.endsWith('.tsx'))
|
|
55
|
+
);
|
|
56
|
+
implementationFiles.push(...fileList);
|
|
57
|
+
}
|
|
58
|
+
} catch (err) {
|
|
59
|
+
// Skip failed commits
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
} catch (err) {
|
|
65
|
+
// If git fails, use empty array
|
|
66
|
+
console.warn(`Warning: Could not retrieve git history for feature #${featureId}`);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Remove duplicates
|
|
70
|
+
implementationFiles = [...new Set(implementationFiles)];
|
|
71
|
+
|
|
72
|
+
// Check if no implementation files found
|
|
73
|
+
const warning = implementationFiles.length === 0
|
|
74
|
+
? 'No git commits found for this feature'
|
|
75
|
+
: null;
|
|
76
|
+
|
|
77
|
+
// Analyze files for production concerns
|
|
78
|
+
const productionGaps = {
|
|
79
|
+
security: [],
|
|
80
|
+
scale: [],
|
|
81
|
+
compliance: []
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
for (const filePath of implementationFiles) {
|
|
85
|
+
const fullPath = path.join(process.cwd(), filePath);
|
|
86
|
+
|
|
87
|
+
if (!fs.existsSync(fullPath)) {
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
try {
|
|
92
|
+
const content = fs.readFileSync(fullPath, 'utf8');
|
|
93
|
+
|
|
94
|
+
// Security analysis
|
|
95
|
+
if (content.includes('POST') || content.includes('PUT') || content.includes('DELETE')) {
|
|
96
|
+
productionGaps.security.push(`Rate limiting needed on ${filePath}`);
|
|
97
|
+
}
|
|
98
|
+
if (content.includes('password') || content.includes('auth') || content.includes('login')) {
|
|
99
|
+
productionGaps.security.push(`Input sanitization for SQL injection prevention in ${filePath}`);
|
|
100
|
+
}
|
|
101
|
+
if (content.includes('api') || content.includes('endpoint')) {
|
|
102
|
+
productionGaps.security.push(`Authentication/authorization checks in ${filePath}`);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Scale analysis
|
|
106
|
+
if (content.includes('db.') || content.includes('database') || content.includes('query')) {
|
|
107
|
+
productionGaps.scale.push(`Connection pooling needed in ${filePath}`);
|
|
108
|
+
}
|
|
109
|
+
if (content.includes('fetch') || content.includes('http') || content.includes('request')) {
|
|
110
|
+
productionGaps.scale.push(`Caching strategy for ${filePath}`);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Compliance analysis
|
|
114
|
+
if (content.includes('user') || content.includes('data') || content.includes('record')) {
|
|
115
|
+
productionGaps.compliance.push(`Audit logging for user actions in ${filePath}`);
|
|
116
|
+
}
|
|
117
|
+
if (content.includes('email') || content.includes('phone') || content.includes('address')) {
|
|
118
|
+
productionGaps.compliance.push(`Data retention policy for ${filePath}`);
|
|
119
|
+
}
|
|
120
|
+
} catch (readErr) {
|
|
121
|
+
// Skip files that can't be read
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Remove duplicates from gaps
|
|
127
|
+
productionGaps.security = [...new Set(productionGaps.security)];
|
|
128
|
+
productionGaps.scale = [...new Set(productionGaps.scale)];
|
|
129
|
+
productionGaps.compliance = [...new Set(productionGaps.compliance)];
|
|
130
|
+
|
|
131
|
+
return {
|
|
132
|
+
filesAnalyzed: implementationFiles,
|
|
133
|
+
productionGaps,
|
|
134
|
+
warning
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Generate production chore proposals from analysis
|
|
140
|
+
* @param {Object} analysisResult - Result from analyzeImplementation
|
|
141
|
+
* @param {string} featureTitle - Title of the feature
|
|
142
|
+
* @returns {Array<Object>} Array of {title, description} chore proposals
|
|
143
|
+
*/
|
|
144
|
+
function generateProductionChores(analysisResult, featureTitle) {
|
|
145
|
+
const chores = [];
|
|
146
|
+
|
|
147
|
+
// Security chores
|
|
148
|
+
if (analysisResult.productionGaps.security.length > 0) {
|
|
149
|
+
const securityGaps = analysisResult.productionGaps.security.slice(0, 3); // Top 3
|
|
150
|
+
chores.push({
|
|
151
|
+
title: `Add security hardening to ${featureTitle}`,
|
|
152
|
+
description: `Implement security measures for production readiness:\n\n${securityGaps.map(gap => `• ${gap}`).join('\n')}\n\nFiles analyzed: ${analysisResult.filesAnalyzed.join(', ')}\nGap: Security - Rate limiting, input sanitization, auth checks`
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Scale chores
|
|
157
|
+
if (analysisResult.productionGaps.scale.length > 0) {
|
|
158
|
+
const scaleGaps = analysisResult.productionGaps.scale.slice(0, 3); // Top 3
|
|
159
|
+
chores.push({
|
|
160
|
+
title: `Add scale testing to ${featureTitle}`,
|
|
161
|
+
description: `Optimize and test for production scale:\n\n${scaleGaps.map(gap => `• ${gap}`).join('\n')}\n\nFiles analyzed: ${analysisResult.filesAnalyzed.join(', ')}\nGap: Scale - Connection pooling, caching, load testing`
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Compliance chores
|
|
166
|
+
if (analysisResult.productionGaps.compliance.length > 0) {
|
|
167
|
+
const complianceGaps = analysisResult.productionGaps.compliance.slice(0, 3); // Top 3
|
|
168
|
+
chores.push({
|
|
169
|
+
title: `Add compliance requirements to ${featureTitle}`,
|
|
170
|
+
description: `Ensure compliance and audit trail for production:\n\n${complianceGaps.map(gap => `• ${gap}`).join('\n')}\n\nFiles analyzed: ${analysisResult.filesAnalyzed.join(', ')}\nGap: Compliance - Audit logging, data retention, regulatory requirements`
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// If no gaps found, create generic production chores
|
|
175
|
+
if (chores.length === 0) {
|
|
176
|
+
chores.push(
|
|
177
|
+
{
|
|
178
|
+
title: `Add security hardening to ${featureTitle}`,
|
|
179
|
+
description: `Add authentication/authorization checks, input sanitization, encryption, and attack protection (XSS, CSRF, injection) for ${featureTitle}\n\nFiles: ${analysisResult.filesAnalyzed.join(', ')}\nGap: Security - General hardening needed`
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
title: `Add scale testing to ${featureTitle}`,
|
|
183
|
+
description: `Add performance optimization, load testing (100+ concurrent users), caching strategy, and monitoring for ${featureTitle}\n\nFiles: ${analysisResult.filesAnalyzed.join(', ')}\nGap: Scale - Performance testing needed`
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
title: `Add compliance requirements to ${featureTitle}`,
|
|
187
|
+
description: `Add GDPR/HIPAA/SOC2 compliance, audit trails, data retention policies, and regulatory requirements for ${featureTitle}\n\nFiles: ${analysisResult.filesAnalyzed.join(', ')}\nGap: Compliance - Compliance checks needed`
|
|
188
|
+
}
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return chores;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
module.exports = {
|
|
196
|
+
analyzeImplementation,
|
|
197
|
+
generateProductionChores
|
|
198
|
+
};
|