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 +65 -0
- package/lib/git-hooks/pre-commit +1 -1
- package/lib/work-commands/index.js +250 -1
- package/lib/worktree-manager.js +3 -2
- package/package.json +1 -1
- package/skills-templates/bug-mode/SKILL.md +2 -2
- package/skills-templates/epic-planning/SKILL.md +38 -9
- package/skills-templates/feature-planning/SKILL.md +38 -2
- package/skills-templates/speed-mode/SKILL.md +5 -6
- package/skills-templates/stable-mode/SKILL.md +3 -4
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
|
package/lib/git-hooks/pre-commit
CHANGED
|
@@ -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('
|
|
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
|
};
|
package/lib/worktree-manager.js
CHANGED
|
@@ -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
|
@@ -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
|
|
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
|
|
235
|
-
|
|
236
|
-
|
|
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
|
-
|
|
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
|
-
|
|
256
|
+
cd <worktree_path>
|
|
257
|
+
git add .
|
|
258
|
+
git commit -m "Add prototype: [approach-name] for epic #${EPIC_ID}"
|
|
242
259
|
```
|
|
243
260
|
|
|
244
|
-
|
|
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
|
-
|
|
275
|
+
This merges prototype files to main (in `/prototypes/` directory) and cleans up the worktree.
|
|
247
276
|
|
|
248
|
-
|
|
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
|
|
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. **
|
|
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
|
|
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 (
|
|
971
|
-
jettypod work merge <id>
|
|
972
|
-
jettypod work merge
|
|
973
|
-
jettypod work merge --
|
|
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 (
|
|
783
|
-
jettypod work merge <id> # Merge chore by ID
|
|
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
|
|
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
|
|