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.
@@ -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 work tree: ${err.message}`));
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-discover ${row.id}`);
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-discover ' + newId);
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-discover ${parentId}`);
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 work tree --expand=1 Show details for item #1');
760
- console.log(' jettypod work tree --expand=1,2,3 Show details for multiple items');
761
- console.log(' jettypod work tree --expand-all Show all details');
762
- console.log(' jettypod work tree Collapse all (default)');
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 work tree: ${err.message}`);
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 work tree` to see all work items');
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-discover': {
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-discover <epic-id>');
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-discover skill to guide you through:');
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-discover/SKILL.md');
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
- if (feature.phase !== 'discovery') {
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-discover skill');
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
- // Transition to implementation phase, set mode to speed
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
- `UPDATE work_items SET phase = 'implementation', mode = 'speed', prototype_files = ?, discovery_winner = ?, discovery_rationale = ? WHERE id = ?`,
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
- console.log(`✅ Feature #${featureId} transitioned to Implementation Phase`);
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-discover skill to guide you through:');
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-discover/SKILL.md');
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-discover skill to guide you through:');
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-discover/SKILL.md');
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 work tree
1473
- Show hierarchical view (active items only)
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 work tree
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 work tree
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 work tree\` to count stable mode features
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 work tree\` and press Enter - you'll see a visual tree of all your work."
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-discover skill:**
581
- Use the Skill tool to invoke: epic-discover
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 work tree' to see all items`);
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-discover <epic-id>');
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 work tree Show all work items
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
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jettypod",
3
- "version": "3.0.3",
3
+ "version": "4.0.0",
4
4
  "description": "Simplified development mode manager",
5
5
  "main": "jettypod.js",
6
6
  "bin": {