jettypod 4.4.93 → 4.4.95

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
@@ -289,6 +289,8 @@ jettypod work merge # Merges worktree back to main
289
289
  jettypod work cleanup <id> # Cleanup worktree after merge
290
290
  jettypod work tests <feature-id> # Create test worktree for BDD
291
291
  jettypod work tests merge <id> # Merge tests to main
292
+ jettypod work prototype start <id> <approach> # Create prototype worktree
293
+ jettypod work prototype merge <id> # Merge prototype to main
292
294
  jettypod work status <id> cancelled
293
295
  jettypod backlog
294
296
  jettypod impact <file> # Show tests/features affected
@@ -1506,6 +1508,69 @@ switch (command) {
1506
1508
  process.exit(1);
1507
1509
  }
1508
1510
  }
1511
+ } else if (subcommand === 'prototype') {
1512
+ // Handle: work prototype start <feature-id> <approach-name> OR work prototype merge <feature-id>
1513
+ const workCommands = require('./lib/work-commands/index.js');
1514
+
1515
+ if (args[1] === 'merge') {
1516
+ // work prototype merge <feature-id>
1517
+ const featureId = parseInt(args[2]);
1518
+ if (!featureId) {
1519
+ console.log('Usage: jettypod work prototype merge <feature-id>');
1520
+ console.log('');
1521
+ console.log('Merges a prototype worktree back to main and cleans up.');
1522
+ console.log('');
1523
+ console.log('This command:');
1524
+ console.log(' 1. Verifies all changes are committed');
1525
+ console.log(' 2. Merges the prototype branch to main');
1526
+ console.log(' 3. Pushes to remote');
1527
+ console.log(' 4. Removes the worktree');
1528
+ console.log(' 5. Deletes the prototype branch');
1529
+ process.exit(1);
1530
+ }
1531
+ try {
1532
+ await workCommands.prototypeMerge(featureId);
1533
+ } catch (err) {
1534
+ console.error(`Error: ${err.message}`);
1535
+ process.exit(1);
1536
+ }
1537
+ } else if (args[1] === 'start') {
1538
+ // work prototype start <feature-id> <approach-name>
1539
+ const featureId = parseInt(args[2]);
1540
+ const approachName = args[3];
1541
+ if (!featureId || !approachName) {
1542
+ console.log('Usage: jettypod work prototype start <feature-id> <approach-name>');
1543
+ console.log('');
1544
+ console.log('Creates a worktree for building prototypes for a feature.');
1545
+ console.log('');
1546
+ console.log('Examples:');
1547
+ console.log(' jettypod work prototype start 42 simple-form');
1548
+ console.log(' jettypod work prototype start 42 wizard-flow');
1549
+ console.log('');
1550
+ console.log('Prototypes are written in /prototypes/feature-<id>-<approach>/ directory.');
1551
+ console.log('When done, merge with: jettypod work prototype merge <feature-id>');
1552
+ process.exit(1);
1553
+ }
1554
+ try {
1555
+ await workCommands.prototypeWork(featureId, approachName);
1556
+ } catch (err) {
1557
+ console.error(`Error: ${err.message}`);
1558
+ process.exit(1);
1559
+ }
1560
+ } else {
1561
+ // work prototype (show usage)
1562
+ console.log('Usage: jettypod work prototype start <feature-id> <approach-name>');
1563
+ console.log(' jettypod work prototype merge <feature-id>');
1564
+ console.log('');
1565
+ console.log('Commands:');
1566
+ console.log(' work prototype start <id> <approach> Create a worktree for prototyping');
1567
+ console.log(' work prototype merge <id> Merge prototype worktree to main');
1568
+ console.log('');
1569
+ console.log('Examples:');
1570
+ console.log(' jettypod work prototype start 42 simple-form');
1571
+ console.log(' jettypod work prototype merge 42');
1572
+ process.exit(1);
1573
+ }
1509
1574
  } else {
1510
1575
  // CRITICAL SAFETY CHECK: Prevent work status changes from within a worktree
1511
1576
  // This prevents cleanup failures and orphaned worktrees
@@ -67,7 +67,7 @@ async function exportSnapshots() {
67
67
  console.log('\nšŸ“ø Exporting database snapshots...\n');
68
68
 
69
69
  try {
70
- const { exportAll } = require('../db-export');
70
+ const { exportAll } = require('__JETTYPOD_ROOT__/lib/db-export');
71
71
  const paths = await exportAll();
72
72
 
73
73
  // Stage the snapshot files
@@ -2126,6 +2126,253 @@ async function testsMerge(featureId) {
2126
2126
  return Promise.resolve();
2127
2127
  }
2128
2128
 
2129
+ /**
2130
+ * Create a worktree for building prototypes for a feature or epic
2131
+ * @param {number} workItemId - Feature or Epic ID to create prototype worktree for
2132
+ * @param {string} approachName - Name of the approach being prototyped (e.g., "simple-form", "wizard")
2133
+ * @returns {Promise<Object>} Worktree info with path and branch
2134
+ * @throws {Error} If work item not found or not a feature/epic
2135
+ */
2136
+ async function prototypeWork(workItemId, approachName) {
2137
+ if (!workItemId || isNaN(workItemId) || workItemId < 1) {
2138
+ return Promise.reject(new Error('Invalid work item ID'));
2139
+ }
2140
+
2141
+ if (!approachName || typeof approachName !== 'string' || !approachName.trim()) {
2142
+ return Promise.reject(new Error('Approach name is required (e.g., "simple-form", "wizard")'));
2143
+ }
2144
+
2145
+ // Sanitize approach name for use in branch/path
2146
+ const sanitizedApproach = approachName.trim().toLowerCase().replace(/[^a-z0-9-]/g, '-');
2147
+
2148
+ const paths = getPaths();
2149
+
2150
+ if (!fs.existsSync(paths.jettypodDir)) {
2151
+ return Promise.reject(new Error('JettyPod not initialized. Run: jettypod init'));
2152
+ }
2153
+
2154
+ if (!fs.existsSync(paths.dbPath)) {
2155
+ return Promise.reject(new Error('Work database not found. Run: jettypod init'));
2156
+ }
2157
+
2158
+ const db = getDb();
2159
+
2160
+ return new Promise((resolve, reject) => {
2161
+ // Get feature or epic work item
2162
+ db.get(
2163
+ `SELECT * FROM work_items WHERE id = ? AND type IN ('feature', 'epic')`,
2164
+ [workItemId],
2165
+ async (err, workItem) => {
2166
+ if (err) {
2167
+ return reject(new Error(`Database error: ${err.message}`));
2168
+ }
2169
+
2170
+ if (!workItem) {
2171
+ return reject(new Error(`Feature or epic #${workItemId} not found`));
2172
+ }
2173
+
2174
+ // Create worktree with prototype/ prefix
2175
+ try {
2176
+ const gitRoot = getGitRoot();
2177
+ const branchPrefix = workItem.type === 'epic' ? 'prototype/epic' : 'prototype/feature';
2178
+ const worktreeResult = await worktreeManager.createWorktree(workItem, {
2179
+ repoPath: gitRoot,
2180
+ branchPrefix: branchPrefix,
2181
+ pathPrefix: `prototype-`,
2182
+ branchSuffix: `-${sanitizedApproach}`
2183
+ });
2184
+
2185
+ // Set as current work
2186
+ setCurrentWork({
2187
+ id: workItem.id,
2188
+ type: workItem.type,
2189
+ title: workItem.title,
2190
+ status: workItem.status || 'in_progress',
2191
+ worktreePath: worktreeResult.worktree_path,
2192
+ branchName: worktreeResult.branch_name
2193
+ });
2194
+
2195
+ console.log(`āœ… Created prototype worktree: ${worktreeResult.worktree_path}`);
2196
+ console.log(`šŸ“ IMPORTANT: Use absolute paths for file operations:`);
2197
+ console.log(` ${worktreeResult.worktree_path}/prototypes/${workItem.type}-${workItemId}-${sanitizedApproach}/ (correct)`);
2198
+ console.log(` prototypes/... (wrong - creates in main repo)`);
2199
+ console.log(`\nPrototyping "${approachName}" for: [#${workItem.id}] ${workItem.title}`);
2200
+
2201
+ resolve(worktreeResult);
2202
+ } catch (worktreeErr) {
2203
+ reject(new Error(`Failed to create prototype worktree: ${worktreeErr.message}`));
2204
+ }
2205
+ }
2206
+ );
2207
+ });
2208
+ }
2209
+
2210
+ /**
2211
+ * Merge a prototype worktree back to main and clean up
2212
+ * @param {number} workItemId - Feature or Epic ID whose prototype worktree to merge
2213
+ * @returns {Promise<void>}
2214
+ */
2215
+ async function prototypeMerge(workItemId) {
2216
+ if (!workItemId || isNaN(workItemId) || workItemId < 1) {
2217
+ return Promise.reject(new Error('Invalid work item ID'));
2218
+ }
2219
+
2220
+ // Auto-chdir to main repo if running from inside a worktree
2221
+ const cwd = process.cwd();
2222
+ if (cwd.includes('.jettypod-work')) {
2223
+ const mainRepo = getGitRoot();
2224
+ console.log('šŸ“‚ Changing to main repository for merge...');
2225
+ process.chdir(mainRepo);
2226
+ }
2227
+
2228
+ const db = getDb();
2229
+ const gitRoot = getGitRoot();
2230
+
2231
+ // Find the prototype worktree for this feature or epic (branch starts with prototype/feature- or prototype/epic-)
2232
+ const worktree = await new Promise((resolve, reject) => {
2233
+ db.get(
2234
+ `SELECT * FROM worktrees
2235
+ WHERE work_item_id = ?
2236
+ AND (branch_name LIKE 'prototype/feature-%' OR branch_name LIKE 'prototype/epic-%')
2237
+ AND status = 'active'`,
2238
+ [workItemId],
2239
+ (err, row) => {
2240
+ if (err) return reject(err);
2241
+ resolve(row);
2242
+ }
2243
+ );
2244
+ });
2245
+
2246
+ if (!worktree) {
2247
+ return Promise.reject(new Error(
2248
+ `No active prototype worktree found for #${workItemId}.\n\n` +
2249
+ `Create one with: jettypod work prototype start ${workItemId} <approach-name>`
2250
+ ));
2251
+ }
2252
+
2253
+ const worktreePath = worktree.worktree_path;
2254
+ const branchName = worktree.branch_name;
2255
+
2256
+ console.log(`ā³ Merging prototype worktree for #${workItemId}...`);
2257
+ console.log(` Branch: ${branchName}`);
2258
+ console.log(` Path: ${worktreePath}`);
2259
+
2260
+ // Check for uncommitted changes in the worktree
2261
+ try {
2262
+ const status = execSync('git status --porcelain', {
2263
+ cwd: worktreePath,
2264
+ encoding: 'utf8',
2265
+ stdio: 'pipe'
2266
+ }).trim();
2267
+
2268
+ if (status) {
2269
+ return Promise.reject(new Error(
2270
+ `Uncommitted changes in prototype worktree:\n${status}\n\n` +
2271
+ `Commit your changes first:\n` +
2272
+ ` cd ${worktreePath}\n` +
2273
+ ` git add .\n` +
2274
+ ` git commit -m "Add prototype for #${workItemId}"`
2275
+ ));
2276
+ }
2277
+ } catch (err) {
2278
+ return Promise.reject(new Error(`Failed to check worktree status: ${err.message}`));
2279
+ }
2280
+
2281
+ // Check if there are commits to merge
2282
+ try {
2283
+ const mainBranch = execSync('git symbolic-ref refs/remotes/origin/HEAD', {
2284
+ cwd: gitRoot,
2285
+ encoding: 'utf8',
2286
+ stdio: 'pipe'
2287
+ }).trim().replace('refs/remotes/origin/', '');
2288
+
2289
+ const commitCount = execSync(`git rev-list --count ${mainBranch}..${branchName}`, {
2290
+ cwd: gitRoot,
2291
+ encoding: 'utf8',
2292
+ stdio: 'pipe'
2293
+ }).trim();
2294
+
2295
+ if (commitCount === '0') {
2296
+ console.log('āš ļø No commits to merge - worktree has no new changes');
2297
+ }
2298
+ } catch (err) {
2299
+ // Non-fatal - proceed anyway
2300
+ console.log('āš ļø Could not check commit count, proceeding with merge');
2301
+ }
2302
+
2303
+ // Merge the prototype branch to main
2304
+ try {
2305
+ // First, ensure we're on main
2306
+ execSync('git checkout main', {
2307
+ cwd: gitRoot,
2308
+ encoding: 'utf8',
2309
+ stdio: 'pipe'
2310
+ });
2311
+
2312
+ // Merge the prototype branch
2313
+ execSync(`git merge ${branchName} --no-ff -m "Merge prototype for #${workItemId}"`, {
2314
+ cwd: gitRoot,
2315
+ encoding: 'utf8',
2316
+ stdio: 'pipe'
2317
+ });
2318
+
2319
+ console.log('āœ… Merged prototype branch to main');
2320
+ } catch (err) {
2321
+ return Promise.reject(new Error(
2322
+ `Merge failed: ${err.message}\n\n` +
2323
+ `Resolve conflicts manually, then run:\n` +
2324
+ ` jettypod work prototype merge ${workItemId}`
2325
+ ));
2326
+ }
2327
+
2328
+ // Push to remote
2329
+ try {
2330
+ execSync('git push', {
2331
+ cwd: gitRoot,
2332
+ encoding: 'utf8',
2333
+ stdio: 'pipe'
2334
+ });
2335
+ console.log('āœ… Pushed to remote');
2336
+ } catch (err) {
2337
+ console.log('āš ļø Failed to push (non-fatal):', err.message);
2338
+ }
2339
+
2340
+ // Mark worktree as merged but DON'T delete it yet
2341
+ // This prevents shell CWD corruption when merge is run from inside the worktree
2342
+ await new Promise((resolve, reject) => {
2343
+ db.run(
2344
+ `UPDATE worktrees SET status = 'merged' WHERE id = ?`,
2345
+ [worktree.id],
2346
+ (err) => {
2347
+ if (err) return reject(err);
2348
+ resolve();
2349
+ }
2350
+ );
2351
+ });
2352
+
2353
+ // Clear current work if it was pointing to this worktree
2354
+ const currentWork = await getCurrentWork();
2355
+ if (currentWork && currentWork.worktreePath === worktreePath) {
2356
+ clearCurrentWork();
2357
+ }
2358
+
2359
+ console.log('');
2360
+ console.log(`āœ… Prototype worktree for #${workItemId} merged successfully`);
2361
+
2362
+ // Instruct user to cleanup worktree separately - be aggressive so AI doesn't forget
2363
+ if (fs.existsSync(worktreePath)) {
2364
+ console.log('');
2365
+ console.log('āš ļø CLEANUP REQUIRED - Run these commands NOW:');
2366
+ console.log('');
2367
+ console.log(` cd ${gitRoot}`);
2368
+ console.log(` jettypod work cleanup ${workItemId}`);
2369
+ console.log('');
2370
+ console.log(' Worktrees accumulate until cleaned up.');
2371
+ }
2372
+
2373
+ return Promise.resolve();
2374
+ }
2375
+
2129
2376
  module.exports = {
2130
2377
  startWork,
2131
2378
  stopWork,
@@ -2134,5 +2381,7 @@ module.exports = {
2134
2381
  cleanupWorkItem,
2135
2382
  mergeWork,
2136
2383
  testsWork,
2137
- testsMerge
2384
+ testsMerge,
2385
+ prototypeWork,
2386
+ prototypeMerge
2138
2387
  };
@@ -88,10 +88,11 @@ async function createWorktree(workItem, options = {}) {
88
88
  // Generate branch name and worktree path
89
89
  const titleSlug = slugify(workItem.title || 'item');
90
90
  const branchPrefix = options.branchPrefix || 'feature/work';
91
+ const branchSuffix = options.branchSuffix || '';
91
92
  const pathPrefix = options.pathPrefix || '';
92
- const branchName = `${branchPrefix}-${workItem.id}-${titleSlug}`;
93
+ const branchName = `${branchPrefix}-${workItem.id}-${titleSlug}${branchSuffix}`;
93
94
  const worktreeBasePath = path.join(gitRoot, '.jettypod-work');
94
- const worktreePath = path.join(worktreeBasePath, `${pathPrefix}${workItem.id}-${titleSlug}`);
95
+ const worktreePath = path.join(worktreeBasePath, `${pathPrefix}${workItem.id}-${titleSlug}${branchSuffix}`);
95
96
 
96
97
  let worktreeId = null;
97
98
  let branchCreated = false;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jettypod",
3
- "version": "4.4.93",
3
+ "version": "4.4.95",
4
4
  "description": "AI-powered development workflow manager with TDD, BDD, and automatic test generation",
5
5
  "main": "jettypod.js",
6
6
  "bin": {
@@ -301,7 +301,7 @@ Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>"
301
301
 
302
302
  ```bash
303
303
  # Step 1: Merge (can run from worktree - it won't delete it)
304
- jettypod work merge
304
+ jettypod work merge <bug-id>
305
305
  ```
306
306
 
307
307
  ```bash
@@ -366,7 +366,7 @@ Before ending bug-mode skill, ensure:
366
366
 
367
367
  **Merge fix (CRITICAL: cd to main repo separately):**
368
368
  ```bash
369
- jettypod work merge # Merge current bug
369
+ jettypod work merge <bug-id> # ALWAYS include bug ID
370
370
  ```
371
371
 
372
372
  ```bash
@@ -231,21 +231,50 @@ Present exactly 3 approaches. Fill in the bracketed parts with actual content ba
231
231
 
232
232
  If user wants to prototype approaches:
233
233
 
234
- 1. Create prototype directory using the epic ID from Step 1:
235
- ```bash
236
- mkdir -p prototypes/epic-${EPIC_ID}-${APPROACH_NAME}
237
- ```
234
+ **Sub-step 1: Create prototype worktree**
235
+
236
+ ```bash
237
+ jettypod work prototype start ${EPIC_ID} [approach-name]
238
+ ```
239
+
240
+ Example (if epic ID is 5, approach is "websockets"):
241
+ ```bash
242
+ jettypod work prototype start 5 websockets
243
+ ```
244
+
245
+ This creates a worktree at `.jettypod-work/prototype-<id>-<slug>-<approach>/` where prototypes can be safely built and committed.
246
+
247
+ **šŸ›‘ STOP AND CHECK:** Verify worktree was created successfully before proceeding.
238
248
 
239
- **Example** (if epic ID is 5, approach is "websockets"):
249
+ **Sub-step 2: Build prototypes in the worktree**
250
+
251
+ 1. Build prototypes using **absolute paths** to the worktree:
252
+ - `<worktree_path>/prototypes/epic-${EPIC_ID}-${APPROACH_NAME}/`
253
+ 2. Build 2-3 working prototypes demonstrating the architectural difference
254
+ 3. **Commit the prototype** in the worktree:
240
255
  ```bash
241
- mkdir -p prototypes/epic-5-websockets
256
+ cd <worktree_path>
257
+ git add .
258
+ git commit -m "Add prototype: [approach-name] for epic #${EPIC_ID}"
242
259
  ```
243
260
 
244
- 2. Build 2-3 working prototypes demonstrating the architectural difference
261
+ **Sub-step 3: Test and decide**
262
+
263
+ After user tests, ask which approach they prefer.
264
+
265
+ **Sub-step 4: Merge prototype**
266
+
267
+ After user has tested (regardless of whether they pick this approach):
268
+
269
+ ```bash
270
+ jettypod work prototype merge ${EPIC_ID}
271
+ cd <main-repo-path>
272
+ jettypod work cleanup ${EPIC_ID}
273
+ ```
245
274
 
246
- 3. After user tests, ask which approach they prefer
275
+ This merges prototype files to main (in `/prototypes/` directory) and cleans up the worktree.
247
276
 
248
- 4. When user picks a winner → Go to Step 6 (set `ARCH_DECISION_MADE = true`)
277
+ When user picks a winner → Go to Step 6 (set `ARCH_DECISION_MADE = true`)
249
278
 
250
279
  ### Step 5C: Skip Architecture (User Already Knows)
251
280
 
@@ -141,7 +141,25 @@ Would you like me to create working prototypes of these approaches?
141
141
 
142
142
  If user wants prototypes:
143
143
 
144
- 1. **Build prototypes** in `/prototypes/feature-[id]-[approach-name]/`
144
+ **Sub-step 1: Create prototype worktree**
145
+
146
+ ```bash
147
+ jettypod work prototype start ${FEATURE_ID} [approach-name]
148
+ ```
149
+
150
+ Example:
151
+ ```bash
152
+ jettypod work prototype start 42 simple-form
153
+ ```
154
+
155
+ This creates a worktree at `.jettypod-work/prototype-<id>-<slug>-<approach>/` where prototypes can be safely built and committed.
156
+
157
+ **šŸ›‘ STOP AND CHECK:** Verify worktree was created successfully before proceeding.
158
+
159
+ **Sub-step 2: Build prototypes in the worktree**
160
+
161
+ 1. **Build prototypes** using **absolute paths** to the worktree:
162
+ - `<worktree_path>/prototypes/feature-[id]-[approach-name]/`
145
163
  2. **Name format**: `YYYY-MM-DD-[feature-slug]-[option].ext`
146
164
  3. **Focus on UX**: Show the feel, not production code
147
165
  4. **Add visible banner header at TOP of page** (for HTML/web prototypes):
@@ -156,10 +174,28 @@ If user wants prototypes:
156
174
  </div>
157
175
  ```
158
176
  For CLI/terminal prototypes, add similar info as first output.
159
- 5. **Offer to open them**: "Want me to open these in your browser?"
177
+ 5. **Commit the prototype** in the worktree:
178
+ ```bash
179
+ cd <worktree_path>
180
+ git add .
181
+ git commit -m "Add prototype: [approach-name] for feature #${FEATURE_ID}"
182
+ ```
183
+ 6. **Offer to open them**: "Want me to open these in your browser?"
160
184
 
161
185
  **WAIT for user to test prototypes.**
162
186
 
187
+ **Sub-step 3: Merge prototype after testing**
188
+
189
+ After user has tested (regardless of whether they pick this approach):
190
+
191
+ ```bash
192
+ jettypod work prototype merge ${FEATURE_ID}
193
+ cd <main-repo-path>
194
+ jettypod work cleanup ${FEATURE_ID}
195
+ ```
196
+
197
+ This merges prototype files to main (in `/prototypes/` directory) and cleans up the worktree.
198
+
163
199
  <details>
164
200
  <summary><strong>šŸ“‹ Prototyping Guidelines (click to expand)</strong></summary>
165
201
 
@@ -862,7 +862,7 @@ cd <main-repo> # Bash call 1
862
862
  ```
863
863
 
864
864
  ```bash
865
- jettypod work merge --release-lock # Bash call 2
865
+ jettypod work merge <last-chore-id> --release-lock # Bash call 2
866
866
  ```
867
867
 
868
868
  **šŸ”„ WORKFLOW COMPLETE: Speed mode finished**
@@ -967,11 +967,10 @@ Before ending speed-mode skill, ensure:
967
967
  cd <main-repo>
968
968
  ```
969
969
  ```bash
970
- # Bash call 2: Merge (pick one)
971
- jettypod work merge <id> # Merge chore by ID (recommended)
972
- jettypod work merge # Merge current chore
973
- jettypod work merge --with-transition # Hold lock for transition
974
- jettypod work merge --release-lock # Release held lock
970
+ # Bash call 2: Merge (ALWAYS include chore ID)
971
+ jettypod work merge <chore-id> # Merge chore by ID
972
+ jettypod work merge <chore-id> --with-transition # Hold lock for transition
973
+ jettypod work merge <chore-id> --release-lock # Release held lock
975
974
  ```
976
975
 
977
976
  **Start chores:**
@@ -779,9 +779,8 @@ Before ending stable-mode skill, ensure:
779
779
  cd <main-repo>
780
780
  ```
781
781
  ```bash
782
- # Bash call 2: Merge (pick one)
783
- jettypod work merge <id> # Merge chore by ID (recommended)
784
- jettypod work merge # Merge current chore
782
+ # Bash call 2: Merge (ALWAYS include chore ID)
783
+ jettypod work merge <chore-id> # Merge chore by ID
785
784
  ```
786
785
 
787
786
  **Start chores:**
@@ -811,7 +810,7 @@ jettypod work set-mode <feature-id> production # Set feature to production mode
811
810
  - Parallel worktrees branch from main independently
812
811
 
813
812
  **Process:**
814
- 1. Complete chore → run `cd <main-repo>` then `jettypod work merge` as SEPARATE Bash calls
813
+ 1. Complete chore → run `cd <main-repo>` then `jettypod work merge <chore-id>` as SEPARATE Bash calls
815
814
  2. Start next chore → `jettypod work start <next-id>`
816
815
  3. Repeat
817
816