jettypod 4.4.47 → 4.4.49

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/jettypod.js CHANGED
@@ -1304,21 +1304,67 @@ switch (command) {
1304
1304
  process.exit(1);
1305
1305
  }
1306
1306
  } else if (subcommand === 'tests') {
1307
- // Create worktree for writing BDD tests
1308
- const featureId = parseInt(args[1]);
1309
- if (!featureId) {
1310
- console.log('Usage: jettypod work tests <feature-id>');
1311
- console.log('');
1312
- console.log('Creates a worktree for writing BDD tests for a feature.');
1313
- console.log('Tests are written in isolation, then merged with: jettypod work merge');
1314
- process.exit(1);
1315
- }
1307
+ // Handle: work tests <feature-id> OR work tests merge <feature-id>
1316
1308
  const workCommands = require('./lib/work-commands/index.js');
1317
- try {
1318
- await workCommands.testsWork(featureId);
1319
- } catch (err) {
1320
- console.error(`Error: ${err.message}`);
1321
- process.exit(1);
1309
+
1310
+ if (args[1] === 'merge') {
1311
+ // work tests merge <feature-id>
1312
+ const featureId = parseInt(args[2]);
1313
+ if (!featureId) {
1314
+ console.log('Usage: jettypod work tests merge <feature-id>');
1315
+ console.log('');
1316
+ console.log('Merges a test worktree back to main and cleans up.');
1317
+ console.log('');
1318
+ console.log('This command:');
1319
+ console.log(' 1. Verifies all changes are committed');
1320
+ console.log(' 2. Merges the test branch to main');
1321
+ console.log(' 3. Pushes to remote');
1322
+ console.log(' 4. Removes the worktree');
1323
+ console.log(' 5. Deletes the test branch');
1324
+ process.exit(1);
1325
+ }
1326
+ try {
1327
+ await workCommands.testsMerge(featureId);
1328
+ } catch (err) {
1329
+ console.error(`Error: ${err.message}`);
1330
+ process.exit(1);
1331
+ }
1332
+ } else if (args[1] === 'start') {
1333
+ // work tests start <feature-id> (alias for work tests <feature-id>)
1334
+ const featureId = parseInt(args[2]);
1335
+ if (!featureId) {
1336
+ console.log('Usage: jettypod work tests start <feature-id>');
1337
+ console.log('');
1338
+ console.log('Creates a worktree for writing BDD tests for a feature.');
1339
+ console.log('Tests are written in isolation, then merged with: jettypod work tests merge <feature-id>');
1340
+ process.exit(1);
1341
+ }
1342
+ try {
1343
+ await workCommands.testsWork(featureId);
1344
+ } catch (err) {
1345
+ console.error(`Error: ${err.message}`);
1346
+ process.exit(1);
1347
+ }
1348
+ } else {
1349
+ // work tests <feature-id> (create worktree)
1350
+ const featureId = parseInt(args[1]);
1351
+ if (!featureId) {
1352
+ console.log('Usage: jettypod work tests <feature-id>');
1353
+ console.log(' jettypod work tests start <feature-id>');
1354
+ console.log(' jettypod work tests merge <feature-id>');
1355
+ console.log('');
1356
+ console.log('Commands:');
1357
+ console.log(' work tests <id> Create a worktree for writing BDD tests');
1358
+ console.log(' work tests start <id> Same as above (explicit alias)');
1359
+ console.log(' work tests merge <id> Merge test worktree to main and clean up');
1360
+ process.exit(1);
1361
+ }
1362
+ try {
1363
+ await workCommands.testsWork(featureId);
1364
+ } catch (err) {
1365
+ console.error(`Error: ${err.message}`);
1366
+ process.exit(1);
1367
+ }
1322
1368
  }
1323
1369
  } else {
1324
1370
  // CRITICAL SAFETY CHECK: Prevent work status changes from within a worktree
@@ -1700,11 +1700,196 @@ async function testsWork(featureId) {
1700
1700
  });
1701
1701
  }
1702
1702
 
1703
+ /**
1704
+ * Merge a test worktree back to main and clean up
1705
+ * @param {number} featureId - Feature ID whose test worktree to merge
1706
+ * @returns {Promise<void>}
1707
+ */
1708
+ async function testsMerge(featureId) {
1709
+ if (!featureId || isNaN(featureId) || featureId < 1) {
1710
+ return Promise.reject(new Error('Invalid feature ID'));
1711
+ }
1712
+
1713
+ // CRITICAL: Refuse to run from inside a worktree
1714
+ const cwd = process.cwd();
1715
+ if (cwd.includes('.jettypod-work')) {
1716
+ const mainRepo = getGitRoot();
1717
+ console.error('❌ Cannot merge from inside a worktree.');
1718
+ console.error('');
1719
+ console.error(' The merge will delete this worktree, breaking your shell session.');
1720
+ console.error('');
1721
+ console.error(' Run from the main repository instead:');
1722
+ console.error(` cd ${mainRepo}`);
1723
+ console.error(` jettypod work tests merge ${featureId}`);
1724
+ return Promise.reject(new Error('Cannot merge from inside a worktree'));
1725
+ }
1726
+
1727
+ const db = getDb();
1728
+ const gitRoot = getGitRoot();
1729
+
1730
+ // Find the test worktree for this feature (branch starts with tests/feature-)
1731
+ const worktree = await new Promise((resolve, reject) => {
1732
+ db.get(
1733
+ `SELECT * FROM worktrees
1734
+ WHERE work_item_id = ?
1735
+ AND branch_name LIKE 'tests/feature-%'
1736
+ AND status = 'active'`,
1737
+ [featureId],
1738
+ (err, row) => {
1739
+ if (err) return reject(err);
1740
+ resolve(row);
1741
+ }
1742
+ );
1743
+ });
1744
+
1745
+ if (!worktree) {
1746
+ return Promise.reject(new Error(
1747
+ `No active test worktree found for feature #${featureId}.\n\n` +
1748
+ `Create one with: jettypod work tests ${featureId}`
1749
+ ));
1750
+ }
1751
+
1752
+ const worktreePath = worktree.worktree_path;
1753
+ const branchName = worktree.branch_name;
1754
+
1755
+ console.log(`⏳ Merging test worktree for feature #${featureId}...`);
1756
+ console.log(` Branch: ${branchName}`);
1757
+ console.log(` Path: ${worktreePath}`);
1758
+
1759
+ // Check for uncommitted changes in the worktree
1760
+ try {
1761
+ const status = execSync('git status --porcelain', {
1762
+ cwd: worktreePath,
1763
+ encoding: 'utf8',
1764
+ stdio: 'pipe'
1765
+ }).trim();
1766
+
1767
+ if (status) {
1768
+ return Promise.reject(new Error(
1769
+ `Uncommitted changes in test worktree:\n${status}\n\n` +
1770
+ `Commit your changes first:\n` +
1771
+ ` cd ${worktreePath}\n` +
1772
+ ` git add .\n` +
1773
+ ` git commit -m "Add BDD tests for feature #${featureId}"`
1774
+ ));
1775
+ }
1776
+ } catch (err) {
1777
+ return Promise.reject(new Error(`Failed to check worktree status: ${err.message}`));
1778
+ }
1779
+
1780
+ // Check if there are commits to merge
1781
+ try {
1782
+ const mainBranch = execSync('git symbolic-ref refs/remotes/origin/HEAD', {
1783
+ cwd: gitRoot,
1784
+ encoding: 'utf8',
1785
+ stdio: 'pipe'
1786
+ }).trim().replace('refs/remotes/origin/', '');
1787
+
1788
+ const commitCount = execSync(`git rev-list --count ${mainBranch}..${branchName}`, {
1789
+ cwd: gitRoot,
1790
+ encoding: 'utf8',
1791
+ stdio: 'pipe'
1792
+ }).trim();
1793
+
1794
+ if (commitCount === '0') {
1795
+ console.log('⚠️ No commits to merge - worktree has no new changes');
1796
+ }
1797
+ } catch (err) {
1798
+ // Non-fatal - proceed anyway
1799
+ console.log('⚠️ Could not check commit count, proceeding with merge');
1800
+ }
1801
+
1802
+ // Merge the test branch to main
1803
+ try {
1804
+ // First, ensure we're on main
1805
+ execSync('git checkout main', {
1806
+ cwd: gitRoot,
1807
+ encoding: 'utf8',
1808
+ stdio: 'pipe'
1809
+ });
1810
+
1811
+ // Merge the test branch
1812
+ execSync(`git merge ${branchName} --no-ff -m "Merge BDD tests for feature #${featureId}"`, {
1813
+ cwd: gitRoot,
1814
+ encoding: 'utf8',
1815
+ stdio: 'pipe'
1816
+ });
1817
+
1818
+ console.log('✅ Merged test branch to main');
1819
+ } catch (err) {
1820
+ return Promise.reject(new Error(
1821
+ `Merge failed: ${err.message}\n\n` +
1822
+ `Resolve conflicts manually, then run:\n` +
1823
+ ` jettypod work tests merge ${featureId}`
1824
+ ));
1825
+ }
1826
+
1827
+ // Push to remote
1828
+ try {
1829
+ execSync('git push', {
1830
+ cwd: gitRoot,
1831
+ encoding: 'utf8',
1832
+ stdio: 'pipe'
1833
+ });
1834
+ console.log('✅ Pushed to remote');
1835
+ } catch (err) {
1836
+ console.log('⚠️ Failed to push (non-fatal):', err.message);
1837
+ }
1838
+
1839
+ // Clean up the worktree
1840
+ try {
1841
+ execSync(`git worktree remove "${worktreePath}" --force`, {
1842
+ cwd: gitRoot,
1843
+ encoding: 'utf8',
1844
+ stdio: 'pipe'
1845
+ });
1846
+ console.log('✅ Removed worktree directory');
1847
+ } catch (err) {
1848
+ console.log('⚠️ Failed to remove worktree (non-fatal):', err.message);
1849
+ }
1850
+
1851
+ // Delete the test branch
1852
+ try {
1853
+ execSync(`git branch -d ${branchName}`, {
1854
+ cwd: gitRoot,
1855
+ encoding: 'utf8',
1856
+ stdio: 'pipe'
1857
+ });
1858
+ console.log('✅ Deleted test branch');
1859
+ } catch (err) {
1860
+ console.log('⚠️ Failed to delete branch (non-fatal):', err.message);
1861
+ }
1862
+
1863
+ // Update worktree status in database
1864
+ await new Promise((resolve, reject) => {
1865
+ db.run(
1866
+ `UPDATE worktrees SET status = 'merged' WHERE id = ?`,
1867
+ [worktree.id],
1868
+ (err) => {
1869
+ if (err) return reject(err);
1870
+ resolve();
1871
+ }
1872
+ );
1873
+ });
1874
+
1875
+ // Clear current work if it was pointing to this worktree
1876
+ const currentWork = getCurrentWorkSync();
1877
+ if (currentWork && currentWork.worktreePath === worktreePath) {
1878
+ clearCurrentWork();
1879
+ }
1880
+
1881
+ console.log('');
1882
+ console.log(`✅ Test worktree for feature #${featureId} merged successfully`);
1883
+
1884
+ return Promise.resolve();
1885
+ }
1886
+
1703
1887
  module.exports = {
1704
1888
  startWork,
1705
1889
  stopWork,
1706
1890
  getCurrentWork,
1707
1891
  cleanupWorktrees,
1708
1892
  mergeWork,
1709
- testsWork
1893
+ testsWork,
1894
+ testsMerge
1710
1895
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jettypod",
3
- "version": "4.4.47",
3
+ "version": "4.4.49",
4
4
  "description": "AI-powered development workflow manager with TDD, BDD, and automatic test generation",
5
5
  "main": "jettypod.js",
6
6
  "bin": {
@@ -692,7 +692,7 @@ Added error handling and edge case scenarios for stable mode.
692
692
 
693
693
  # Return to main repo and merge
694
694
  cd <main-repo-path>
695
- jettypod work merge <feature-id>
695
+ jettypod work tests merge <feature-id>
696
696
  ```
697
697
 
698
698
  **7. Present proposal to user:**
@@ -848,8 +848,8 @@ jettypod work start <chore-id> # Create worktree and start chore
848
848
 
849
849
  **Create test worktree (for writing BDD scenarios):**
850
850
  ```bash
851
- jettypod work tests <feature-id> # Create worktree for writing tests
852
- jettypod work merge <feature-id> # Merge tests back to main
851
+ jettypod work tests <feature-id> # Create worktree for writing tests
852
+ jettypod work tests merge <feature-id> # Merge tests back to main
853
853
  ```
854
854
 
855
855
  **Set feature mode (BEFORE creating chores):**