jettypod 4.4.45 → 4.4.46
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 +485 -117
- package/lib/database.js +33 -0
- package/lib/work-commands/index.js +73 -1
- package/lib/worktree-manager.js +6 -2
- package/package.json +1 -1
- package/skills-templates/feature-planning/SKILL.md +226 -144
- package/skills-templates/speed-mode/SKILL.md +31 -13
package/jettypod.js
CHANGED
|
@@ -1303,6 +1303,23 @@ switch (command) {
|
|
|
1303
1303
|
console.error(`Error: ${err.message}`);
|
|
1304
1304
|
process.exit(1);
|
|
1305
1305
|
}
|
|
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
|
+
}
|
|
1316
|
+
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);
|
|
1322
|
+
}
|
|
1306
1323
|
} else {
|
|
1307
1324
|
// CRITICAL SAFETY CHECK: Prevent work status changes from within a worktree
|
|
1308
1325
|
// This prevents cleanup failures and orphaned worktrees
|
|
@@ -2303,76 +2320,127 @@ Quick commands:
|
|
|
2303
2320
|
const workflowSubcommand = args[0];
|
|
2304
2321
|
|
|
2305
2322
|
if (workflowSubcommand === 'status') {
|
|
2323
|
+
// workflow status <work-item-id> - shows skill execution history for a work item
|
|
2324
|
+
const workItemId = args[1] ? parseInt(args[1]) : null;
|
|
2306
2325
|
const { getDb, waitForMigrations } = require('./lib/database');
|
|
2307
|
-
const { getCurrentBranch } = require('./lib/workflow-checkpoint');
|
|
2308
2326
|
|
|
2309
2327
|
try {
|
|
2310
2328
|
const db = getDb();
|
|
2311
2329
|
await waitForMigrations();
|
|
2312
|
-
const currentBranch = getCurrentBranch();
|
|
2313
2330
|
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2331
|
+
if (workItemId) {
|
|
2332
|
+
// Show skill executions for specific work item
|
|
2333
|
+
const executions = await new Promise((resolve, reject) => {
|
|
2334
|
+
db.all(
|
|
2335
|
+
`SELECT * FROM skill_executions WHERE work_item_id = ? ORDER BY started_at ASC`,
|
|
2336
|
+
[workItemId],
|
|
2337
|
+
(err, rows) => {
|
|
2338
|
+
if (err) reject(err);
|
|
2339
|
+
else resolve(rows || []);
|
|
2340
|
+
}
|
|
2341
|
+
);
|
|
2342
|
+
});
|
|
2343
|
+
|
|
2344
|
+
const gates = await new Promise((resolve, reject) => {
|
|
2345
|
+
db.all(
|
|
2346
|
+
`SELECT * FROM workflow_gates WHERE work_item_id = ?`,
|
|
2347
|
+
[workItemId],
|
|
2348
|
+
(err, rows) => {
|
|
2349
|
+
if (err) reject(err);
|
|
2350
|
+
else resolve(rows || []);
|
|
2351
|
+
}
|
|
2352
|
+
);
|
|
2353
|
+
});
|
|
2354
|
+
|
|
2355
|
+
console.log(`Workflow Status for #${workItemId}`);
|
|
2356
|
+
console.log('─'.repeat(50));
|
|
2357
|
+
console.log('');
|
|
2358
|
+
console.log('Skill Executions:');
|
|
2359
|
+
if (executions.length === 0) {
|
|
2360
|
+
console.log(' (none)');
|
|
2361
|
+
} else {
|
|
2362
|
+
for (const exec of executions) {
|
|
2363
|
+
console.log(` ${exec.skill_name}: ${exec.status}`);
|
|
2321
2364
|
}
|
|
2322
|
-
|
|
2323
|
-
});
|
|
2365
|
+
}
|
|
2324
2366
|
|
|
2325
|
-
if (checkpoints.length === 0) {
|
|
2326
|
-
console.log('No interrupted workflows found.');
|
|
2327
|
-
} else {
|
|
2328
|
-
console.log('Interrupted Workflows');
|
|
2329
|
-
console.log('─'.repeat(90));
|
|
2330
2367
|
console.log('');
|
|
2331
|
-
console.log(
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
? `${cp.current_step}/${cp.total_steps}`
|
|
2344
|
-
: `${cp.current_step}`;
|
|
2345
|
-
const isCurrent = cp.branch_name === currentBranch;
|
|
2346
|
-
const branchDisplay = cp.branch_name.length > 35
|
|
2347
|
-
? cp.branch_name.substring(0, 32) + '...'
|
|
2348
|
-
: cp.branch_name;
|
|
2349
|
-
const branchWithMarker = branchDisplay + (isCurrent ? ' *' : '');
|
|
2350
|
-
|
|
2351
|
-
// Format time ago
|
|
2352
|
-
const updatedAt = new Date(cp.updated_at);
|
|
2353
|
-
const now = new Date();
|
|
2354
|
-
const diffMs = now - updatedAt;
|
|
2355
|
-
const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
|
|
2356
|
-
const diffDays = Math.floor(diffHours / 24);
|
|
2357
|
-
const timeAgo = diffDays > 0 ? `${diffDays}d ago` : diffHours > 0 ? `${diffHours}h ago` : 'just now';
|
|
2368
|
+
console.log('Gates:');
|
|
2369
|
+
if (gates.length === 0) {
|
|
2370
|
+
console.log(' (none)');
|
|
2371
|
+
} else {
|
|
2372
|
+
for (const gate of gates) {
|
|
2373
|
+
console.log(` ${gate.gate_name}: ${gate.passed_at ? 'PASSED' : 'NOT PASSED'}`);
|
|
2374
|
+
}
|
|
2375
|
+
}
|
|
2376
|
+
} else {
|
|
2377
|
+
// No ID - show all interrupted workflows (legacy behavior)
|
|
2378
|
+
const { getCurrentBranch } = require('./lib/workflow-checkpoint');
|
|
2379
|
+
const currentBranch = getCurrentBranch();
|
|
2358
2380
|
|
|
2381
|
+
const checkpoints = await new Promise((resolve, reject) => {
|
|
2382
|
+
db.all(
|
|
2383
|
+
`SELECT * FROM workflow_checkpoints ORDER BY updated_at DESC`,
|
|
2384
|
+
[],
|
|
2385
|
+
(err, rows) => {
|
|
2386
|
+
if (err) reject(err);
|
|
2387
|
+
else resolve(rows || []);
|
|
2388
|
+
}
|
|
2389
|
+
);
|
|
2390
|
+
});
|
|
2391
|
+
|
|
2392
|
+
if (checkpoints.length === 0) {
|
|
2393
|
+
console.log('No interrupted workflows found.');
|
|
2394
|
+
} else {
|
|
2395
|
+
console.log('Interrupted Workflows');
|
|
2396
|
+
console.log('─'.repeat(90));
|
|
2397
|
+
console.log('');
|
|
2359
2398
|
console.log(
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2399
|
+
'ID'.padEnd(4) +
|
|
2400
|
+
'Skill'.padEnd(18) +
|
|
2401
|
+
'Step'.padEnd(10) +
|
|
2402
|
+
'Work Item'.padEnd(12) +
|
|
2403
|
+
'Branch'.padEnd(38) +
|
|
2404
|
+
'Last Active'
|
|
2366
2405
|
);
|
|
2367
|
-
|
|
2406
|
+
console.log('─'.repeat(90));
|
|
2407
|
+
|
|
2408
|
+
for (const cp of checkpoints) {
|
|
2409
|
+
const stepInfo = cp.total_steps
|
|
2410
|
+
? `${cp.current_step}/${cp.total_steps}`
|
|
2411
|
+
: `${cp.current_step}`;
|
|
2412
|
+
const isCurrent = cp.branch_name === currentBranch;
|
|
2413
|
+
const branchDisplay = cp.branch_name.length > 35
|
|
2414
|
+
? cp.branch_name.substring(0, 32) + '...'
|
|
2415
|
+
: cp.branch_name;
|
|
2416
|
+
const branchWithMarker = branchDisplay + (isCurrent ? ' *' : '');
|
|
2417
|
+
|
|
2418
|
+
// Format time ago
|
|
2419
|
+
const updatedAt = new Date(cp.updated_at);
|
|
2420
|
+
const now = new Date();
|
|
2421
|
+
const diffMs = now - updatedAt;
|
|
2422
|
+
const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
|
|
2423
|
+
const diffDays = Math.floor(diffHours / 24);
|
|
2424
|
+
const timeAgo = diffDays > 0 ? `${diffDays}d ago` : diffHours > 0 ? `${diffHours}h ago` : 'just now';
|
|
2425
|
+
|
|
2426
|
+
console.log(
|
|
2427
|
+
String(cp.id).padEnd(4) +
|
|
2428
|
+
cp.skill_name.padEnd(18) +
|
|
2429
|
+
stepInfo.padEnd(10) +
|
|
2430
|
+
`#${cp.work_item_id || '-'}`.padEnd(12) +
|
|
2431
|
+
branchWithMarker.padEnd(38) +
|
|
2432
|
+
timeAgo
|
|
2433
|
+
);
|
|
2434
|
+
}
|
|
2368
2435
|
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2436
|
+
console.log('');
|
|
2437
|
+
console.log(`${checkpoints.length} interrupted workflow(s)`);
|
|
2438
|
+
console.log('');
|
|
2439
|
+
console.log('Commands:');
|
|
2440
|
+
console.log(' jettypod workflow show <id> Show workflow details');
|
|
2441
|
+
console.log(' jettypod workflow resume <id> Resume a workflow');
|
|
2442
|
+
console.log(' jettypod workflow abort <id> Abort and clean up a workflow');
|
|
2443
|
+
}
|
|
2376
2444
|
}
|
|
2377
2445
|
} catch (err) {
|
|
2378
2446
|
console.error(`Error: ${err.message}`);
|
|
@@ -2435,73 +2503,44 @@ Quick commands:
|
|
|
2435
2503
|
process.exit(1);
|
|
2436
2504
|
}
|
|
2437
2505
|
} else if (workflowSubcommand === 'resume') {
|
|
2506
|
+
// workflow resume - finds interrupted workflows (in-progress skill executions)
|
|
2438
2507
|
const { getDb, waitForMigrations } = require('./lib/database');
|
|
2439
|
-
const { getCheckpoint, getCurrentBranch } = require('./lib/workflow-checkpoint');
|
|
2440
2508
|
|
|
2441
2509
|
try {
|
|
2442
2510
|
const db = getDb();
|
|
2443
2511
|
await waitForMigrations();
|
|
2444
|
-
const currentBranch = getCurrentBranch();
|
|
2445
|
-
const workflowId = args[1] ? parseInt(args[1]) : null;
|
|
2446
2512
|
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2513
|
+
// Find all in-progress skill executions
|
|
2514
|
+
const inProgressExecutions = await new Promise((resolve, reject) => {
|
|
2515
|
+
db.all(
|
|
2516
|
+
`SELECT se.*, wi.title as work_item_title
|
|
2517
|
+
FROM skill_executions se
|
|
2518
|
+
LEFT JOIN work_items wi ON se.work_item_id = wi.id
|
|
2519
|
+
WHERE se.status = 'in_progress'
|
|
2520
|
+
ORDER BY se.started_at DESC`,
|
|
2521
|
+
[],
|
|
2522
|
+
(err, rows) => {
|
|
2523
|
+
if (err) reject(err);
|
|
2524
|
+
else resolve(rows || []);
|
|
2525
|
+
}
|
|
2526
|
+
);
|
|
2527
|
+
});
|
|
2461
2528
|
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
console.log('');
|
|
2465
|
-
console.log('Use "jettypod workflow status" to see available workflows.');
|
|
2466
|
-
process.exit(1);
|
|
2467
|
-
}
|
|
2529
|
+
if (inProgressExecutions.length === 0) {
|
|
2530
|
+
console.log('No interrupted workflow found');
|
|
2468
2531
|
} else {
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
console.log(
|
|
2532
|
+
for (const exec of inProgressExecutions) {
|
|
2533
|
+
console.log('Found interrupted workflow:');
|
|
2534
|
+
console.log(` Skill: ${exec.skill_name}`);
|
|
2535
|
+
console.log(` Work Item: #${exec.work_item_id}${exec.work_item_title ? ` - ${exec.work_item_title}` : ''}`);
|
|
2536
|
+
console.log(` Step ${exec.step_reached}`);
|
|
2537
|
+
console.log(` Started: ${exec.started_at}`);
|
|
2538
|
+
if (exec.context_json) {
|
|
2539
|
+
console.log(` Context: ${exec.context_json}`);
|
|
2540
|
+
}
|
|
2474
2541
|
console.log('');
|
|
2475
|
-
console.log('Use "jettypod workflow status" to see workflows on other branches,');
|
|
2476
|
-
console.log('or specify an ID: "jettypod workflow resume <id>"');
|
|
2477
|
-
process.exit(0);
|
|
2478
2542
|
}
|
|
2479
2543
|
}
|
|
2480
|
-
|
|
2481
|
-
// Display resume info
|
|
2482
|
-
const stepInfo = checkpoint.total_steps
|
|
2483
|
-
? `Step ${checkpoint.current_step} of ${checkpoint.total_steps}`
|
|
2484
|
-
: `Step ${checkpoint.current_step}`;
|
|
2485
|
-
|
|
2486
|
-
const needsBranchSwitch = checkpoint.branch_name !== currentBranch;
|
|
2487
|
-
|
|
2488
|
-
console.log('Resuming Workflow');
|
|
2489
|
-
console.log('─'.repeat(50));
|
|
2490
|
-
console.log('');
|
|
2491
|
-
console.log(` Skill: ${checkpoint.skill_name}`);
|
|
2492
|
-
console.log(` Progress: ${stepInfo}`);
|
|
2493
|
-
console.log(` Work Item: ${checkpoint.work_item_id ? '#' + checkpoint.work_item_id : '-'}`);
|
|
2494
|
-
console.log(` Branch: ${checkpoint.branch_name}`);
|
|
2495
|
-
console.log('');
|
|
2496
|
-
|
|
2497
|
-
if (needsBranchSwitch) {
|
|
2498
|
-
console.log(`Switching to branch: ${checkpoint.branch_name}`);
|
|
2499
|
-
console.log('');
|
|
2500
|
-
}
|
|
2501
|
-
|
|
2502
|
-
console.log('[Triggering skill continuation]');
|
|
2503
|
-
// Note: Actual skill triggering would be implemented here
|
|
2504
|
-
// For now, this displays the intent to continue the workflow
|
|
2505
2544
|
} catch (err) {
|
|
2506
2545
|
console.error(`Error: ${err.message}`);
|
|
2507
2546
|
process.exit(1);
|
|
@@ -2580,14 +2619,343 @@ Quick commands:
|
|
|
2580
2619
|
console.error(`Error: ${err.message}`);
|
|
2581
2620
|
process.exit(1);
|
|
2582
2621
|
}
|
|
2622
|
+
} else if (workflowSubcommand === 'start') {
|
|
2623
|
+
// workflow start <skill> <work-item-id>
|
|
2624
|
+
// Validates gates, creates skill execution record
|
|
2625
|
+
const skillName = args[1];
|
|
2626
|
+
const workItemId = parseInt(args[2]);
|
|
2627
|
+
|
|
2628
|
+
if (!skillName || !workItemId) {
|
|
2629
|
+
console.log('Usage: jettypod workflow start <skill> <work-item-id>');
|
|
2630
|
+
console.log('');
|
|
2631
|
+
console.log('Starts a skill for a work item after validating prerequisite gates.');
|
|
2632
|
+
console.log('');
|
|
2633
|
+
console.log('Example: jettypod workflow start speed-mode 42');
|
|
2634
|
+
process.exit(1);
|
|
2635
|
+
}
|
|
2636
|
+
|
|
2637
|
+
const { getDb, waitForMigrations } = require('./lib/database');
|
|
2638
|
+
|
|
2639
|
+
try {
|
|
2640
|
+
const db = getDb();
|
|
2641
|
+
await waitForMigrations();
|
|
2642
|
+
|
|
2643
|
+
// Define gate requirements for each skill
|
|
2644
|
+
const gateRequirements = {
|
|
2645
|
+
'speed-mode': ['feature_planning_complete'],
|
|
2646
|
+
'stable-mode': ['speed_mode_complete'],
|
|
2647
|
+
'production-mode': ['stable_mode_complete'],
|
|
2648
|
+
'feature-planning': [],
|
|
2649
|
+
'epic-planning': []
|
|
2650
|
+
};
|
|
2651
|
+
|
|
2652
|
+
const requiredGates = gateRequirements[skillName] || [];
|
|
2653
|
+
|
|
2654
|
+
// Check prerequisite gates
|
|
2655
|
+
const passedGates = await new Promise((resolve, reject) => {
|
|
2656
|
+
db.all(
|
|
2657
|
+
`SELECT gate_name FROM workflow_gates WHERE work_item_id = ? AND passed_at IS NOT NULL`,
|
|
2658
|
+
[workItemId],
|
|
2659
|
+
(err, rows) => {
|
|
2660
|
+
if (err) reject(err);
|
|
2661
|
+
else resolve(rows ? rows.map(r => r.gate_name) : []);
|
|
2662
|
+
}
|
|
2663
|
+
);
|
|
2664
|
+
});
|
|
2665
|
+
|
|
2666
|
+
const missingGates = requiredGates.filter(g => !passedGates.includes(g));
|
|
2667
|
+
|
|
2668
|
+
if (missingGates.length > 0) {
|
|
2669
|
+
console.log(`Gate validation failed: ${missingGates.join(', ')}`);
|
|
2670
|
+
process.exit(1);
|
|
2671
|
+
}
|
|
2672
|
+
|
|
2673
|
+
// Create skill execution record
|
|
2674
|
+
await new Promise((resolve, reject) => {
|
|
2675
|
+
db.run(
|
|
2676
|
+
`INSERT INTO skill_executions (work_item_id, skill_name, status, step_reached)
|
|
2677
|
+
VALUES (?, ?, 'in_progress', 1)`,
|
|
2678
|
+
[workItemId, skillName],
|
|
2679
|
+
(err) => {
|
|
2680
|
+
if (err) reject(err);
|
|
2681
|
+
else resolve();
|
|
2682
|
+
}
|
|
2683
|
+
);
|
|
2684
|
+
});
|
|
2685
|
+
|
|
2686
|
+
console.log(`Started ${skillName} for #${workItemId}`);
|
|
2687
|
+
} catch (err) {
|
|
2688
|
+
console.error(`Error: ${err.message}`);
|
|
2689
|
+
process.exit(1);
|
|
2690
|
+
}
|
|
2691
|
+
} else if (workflowSubcommand === 'complete') {
|
|
2692
|
+
// workflow complete <skill> <work-item-id>
|
|
2693
|
+
// Marks skill execution as completed, passes output gate
|
|
2694
|
+
const skillName = args[1];
|
|
2695
|
+
const workItemId = parseInt(args[2]);
|
|
2696
|
+
|
|
2697
|
+
if (!skillName || !workItemId) {
|
|
2698
|
+
console.log('Usage: jettypod workflow complete <skill> <work-item-id>');
|
|
2699
|
+
console.log('');
|
|
2700
|
+
console.log('Completes a skill and marks its output gate as passed.');
|
|
2701
|
+
console.log('');
|
|
2702
|
+
console.log('Example: jettypod workflow complete speed-mode 42');
|
|
2703
|
+
process.exit(1);
|
|
2704
|
+
}
|
|
2705
|
+
|
|
2706
|
+
const { getDb, waitForMigrations } = require('./lib/database');
|
|
2707
|
+
|
|
2708
|
+
try {
|
|
2709
|
+
const db = getDb();
|
|
2710
|
+
await waitForMigrations();
|
|
2711
|
+
|
|
2712
|
+
// Check if skill is in progress
|
|
2713
|
+
const execution = await new Promise((resolve, reject) => {
|
|
2714
|
+
db.get(
|
|
2715
|
+
`SELECT * FROM skill_executions WHERE work_item_id = ? AND skill_name = ? AND status = 'in_progress'`,
|
|
2716
|
+
[workItemId, skillName],
|
|
2717
|
+
(err, row) => {
|
|
2718
|
+
if (err) reject(err);
|
|
2719
|
+
else resolve(row);
|
|
2720
|
+
}
|
|
2721
|
+
);
|
|
2722
|
+
});
|
|
2723
|
+
|
|
2724
|
+
if (!execution) {
|
|
2725
|
+
console.log(`No in-progress execution found for ${skillName} on #${workItemId}`);
|
|
2726
|
+
process.exit(1);
|
|
2727
|
+
}
|
|
2728
|
+
|
|
2729
|
+
// Update skill execution to completed
|
|
2730
|
+
await new Promise((resolve, reject) => {
|
|
2731
|
+
db.run(
|
|
2732
|
+
`UPDATE skill_executions SET status = 'completed', completed_at = datetime('now')
|
|
2733
|
+
WHERE work_item_id = ? AND skill_name = ? AND status = 'in_progress'`,
|
|
2734
|
+
[workItemId, skillName],
|
|
2735
|
+
(err) => {
|
|
2736
|
+
if (err) reject(err);
|
|
2737
|
+
else resolve();
|
|
2738
|
+
}
|
|
2739
|
+
);
|
|
2740
|
+
});
|
|
2741
|
+
|
|
2742
|
+
// Mark output gate as passed
|
|
2743
|
+
const outputGate = `${skillName.replace(/-/g, '_')}_complete`;
|
|
2744
|
+
await new Promise((resolve, reject) => {
|
|
2745
|
+
db.run(
|
|
2746
|
+
`INSERT OR REPLACE INTO workflow_gates (work_item_id, gate_name, passed_at)
|
|
2747
|
+
VALUES (?, ?, datetime('now'))`,
|
|
2748
|
+
[workItemId, outputGate],
|
|
2749
|
+
(err) => {
|
|
2750
|
+
if (err) reject(err);
|
|
2751
|
+
else resolve();
|
|
2752
|
+
}
|
|
2753
|
+
);
|
|
2754
|
+
});
|
|
2755
|
+
|
|
2756
|
+
console.log(`Completed ${skillName} for #${workItemId}`);
|
|
2757
|
+
} catch (err) {
|
|
2758
|
+
console.error(`Error: ${err.message}`);
|
|
2759
|
+
process.exit(1);
|
|
2760
|
+
}
|
|
2761
|
+
} else if (workflowSubcommand === 'checkpoint') {
|
|
2762
|
+
// workflow checkpoint <work-item-id> --step=N [--context='{"key":"value"}']
|
|
2763
|
+
// Updates step progress and optionally stores context
|
|
2764
|
+
const workItemId = parseInt(args[1]);
|
|
2765
|
+
const stepArg = args.find(a => a.startsWith('--step='));
|
|
2766
|
+
const contextArg = args.find(a => a.startsWith('--context='));
|
|
2767
|
+
|
|
2768
|
+
if (!workItemId) {
|
|
2769
|
+
console.log('Usage: jettypod workflow checkpoint <work-item-id> --step=N [--context=JSON]');
|
|
2770
|
+
console.log('');
|
|
2771
|
+
console.log('Updates checkpoint progress for an in-progress skill execution.');
|
|
2772
|
+
console.log('');
|
|
2773
|
+
console.log('Example: jettypod workflow checkpoint 42 --step=3');
|
|
2774
|
+
console.log('Example: jettypod workflow checkpoint 42 --step=2 --context=\'{"iteration":5}\'');
|
|
2775
|
+
process.exit(1);
|
|
2776
|
+
}
|
|
2777
|
+
|
|
2778
|
+
const { getDb, waitForMigrations } = require('./lib/database');
|
|
2779
|
+
|
|
2780
|
+
try {
|
|
2781
|
+
const db = getDb();
|
|
2782
|
+
await waitForMigrations();
|
|
2783
|
+
|
|
2784
|
+
// Find in-progress skill execution
|
|
2785
|
+
const execution = await new Promise((resolve, reject) => {
|
|
2786
|
+
db.get(
|
|
2787
|
+
`SELECT * FROM skill_executions WHERE work_item_id = ? AND status = 'in_progress'`,
|
|
2788
|
+
[workItemId],
|
|
2789
|
+
(err, row) => {
|
|
2790
|
+
if (err) reject(err);
|
|
2791
|
+
else resolve(row);
|
|
2792
|
+
}
|
|
2793
|
+
);
|
|
2794
|
+
});
|
|
2795
|
+
|
|
2796
|
+
if (!execution) {
|
|
2797
|
+
console.log(`No in-progress skill execution found for #${workItemId}`);
|
|
2798
|
+
process.exit(1);
|
|
2799
|
+
}
|
|
2800
|
+
|
|
2801
|
+
// Build update query
|
|
2802
|
+
const updates = [];
|
|
2803
|
+
const values = [];
|
|
2804
|
+
|
|
2805
|
+
if (stepArg) {
|
|
2806
|
+
const step = parseInt(stepArg.split('=')[1]);
|
|
2807
|
+
updates.push('step_reached = ?');
|
|
2808
|
+
values.push(step);
|
|
2809
|
+
}
|
|
2810
|
+
|
|
2811
|
+
if (contextArg) {
|
|
2812
|
+
const contextJson = contextArg.split('=').slice(1).join('=').replace(/^'|'$/g, '');
|
|
2813
|
+
updates.push('context_json = ?');
|
|
2814
|
+
values.push(contextJson);
|
|
2815
|
+
}
|
|
2816
|
+
|
|
2817
|
+
if (updates.length === 0) {
|
|
2818
|
+
console.log('No updates specified. Use --step=N and/or --context=JSON');
|
|
2819
|
+
process.exit(1);
|
|
2820
|
+
}
|
|
2821
|
+
|
|
2822
|
+
values.push(workItemId);
|
|
2823
|
+
|
|
2824
|
+
await new Promise((resolve, reject) => {
|
|
2825
|
+
db.run(
|
|
2826
|
+
`UPDATE skill_executions SET ${updates.join(', ')} WHERE work_item_id = ? AND status = 'in_progress'`,
|
|
2827
|
+
values,
|
|
2828
|
+
(err) => {
|
|
2829
|
+
if (err) reject(err);
|
|
2830
|
+
else resolve();
|
|
2831
|
+
}
|
|
2832
|
+
);
|
|
2833
|
+
});
|
|
2834
|
+
|
|
2835
|
+
console.log(`Checkpoint updated for #${workItemId}`);
|
|
2836
|
+
} catch (err) {
|
|
2837
|
+
console.error(`Error: ${err.message}`);
|
|
2838
|
+
process.exit(1);
|
|
2839
|
+
}
|
|
2840
|
+
} else if (workflowSubcommand === 'gate') {
|
|
2841
|
+
// workflow gate pass <work-item-id> <gate-name>
|
|
2842
|
+
// workflow gate check <work-item-id>
|
|
2843
|
+
const gateAction = args[1];
|
|
2844
|
+
const workItemId = parseInt(args[2]);
|
|
2845
|
+
|
|
2846
|
+
if (!gateAction || !workItemId) {
|
|
2847
|
+
console.log('Usage: jettypod workflow gate <action> <work-item-id> [gate-name]');
|
|
2848
|
+
console.log('');
|
|
2849
|
+
console.log('Actions:');
|
|
2850
|
+
console.log(' pass <id> <gate> Mark a gate as passed');
|
|
2851
|
+
console.log(' check <id> Show all gates for a work item');
|
|
2852
|
+
console.log('');
|
|
2853
|
+
console.log('Example: jettypod workflow gate pass 42 feature_planning_complete');
|
|
2854
|
+
console.log('Example: jettypod workflow gate check 42');
|
|
2855
|
+
process.exit(1);
|
|
2856
|
+
}
|
|
2857
|
+
|
|
2858
|
+
const { getDb, waitForMigrations } = require('./lib/database');
|
|
2859
|
+
|
|
2860
|
+
try {
|
|
2861
|
+
const db = getDb();
|
|
2862
|
+
await waitForMigrations();
|
|
2863
|
+
|
|
2864
|
+
if (gateAction === 'pass') {
|
|
2865
|
+
const gateName = args[3];
|
|
2866
|
+
|
|
2867
|
+
if (!gateName) {
|
|
2868
|
+
console.log('Usage: jettypod workflow gate pass <work-item-id> <gate-name>');
|
|
2869
|
+
process.exit(1);
|
|
2870
|
+
}
|
|
2871
|
+
|
|
2872
|
+
await new Promise((resolve, reject) => {
|
|
2873
|
+
db.run(
|
|
2874
|
+
`INSERT OR REPLACE INTO workflow_gates (work_item_id, gate_name, passed_at)
|
|
2875
|
+
VALUES (?, ?, datetime('now'))`,
|
|
2876
|
+
[workItemId, gateName],
|
|
2877
|
+
(err) => {
|
|
2878
|
+
if (err) reject(err);
|
|
2879
|
+
else resolve();
|
|
2880
|
+
}
|
|
2881
|
+
);
|
|
2882
|
+
});
|
|
2883
|
+
|
|
2884
|
+
console.log(`Gate passed: ${gateName}`);
|
|
2885
|
+
} else if (gateAction === 'check') {
|
|
2886
|
+
// Get all known gates for this skill workflow
|
|
2887
|
+
const knownGates = [
|
|
2888
|
+
'feature_planning_complete',
|
|
2889
|
+
'speed_mode_complete',
|
|
2890
|
+
'stable_mode_complete',
|
|
2891
|
+
'production_mode_complete'
|
|
2892
|
+
];
|
|
2893
|
+
|
|
2894
|
+
const passedGates = await new Promise((resolve, reject) => {
|
|
2895
|
+
db.all(
|
|
2896
|
+
`SELECT gate_name, passed_at FROM workflow_gates WHERE work_item_id = ?`,
|
|
2897
|
+
[workItemId],
|
|
2898
|
+
(err, rows) => {
|
|
2899
|
+
if (err) reject(err);
|
|
2900
|
+
else resolve(rows || []);
|
|
2901
|
+
}
|
|
2902
|
+
);
|
|
2903
|
+
});
|
|
2904
|
+
|
|
2905
|
+
const passedGateMap = {};
|
|
2906
|
+
for (const g of passedGates) {
|
|
2907
|
+
passedGateMap[g.gate_name] = g.passed_at;
|
|
2908
|
+
}
|
|
2909
|
+
|
|
2910
|
+
console.log(`Gates for #${workItemId}:`);
|
|
2911
|
+
console.log('');
|
|
2912
|
+
|
|
2913
|
+
for (const gateName of knownGates) {
|
|
2914
|
+
if (passedGateMap[gateName]) {
|
|
2915
|
+
console.log(`${gateName}: PASSED`);
|
|
2916
|
+
} else {
|
|
2917
|
+
console.log(`${gateName}: NOT PASSED`);
|
|
2918
|
+
}
|
|
2919
|
+
}
|
|
2920
|
+
|
|
2921
|
+
// Also show any custom gates
|
|
2922
|
+
const customGates = passedGates.filter(g => !knownGates.includes(g.gate_name));
|
|
2923
|
+
for (const g of customGates) {
|
|
2924
|
+
console.log(`${g.gate_name}: PASSED`);
|
|
2925
|
+
}
|
|
2926
|
+
} else {
|
|
2927
|
+
console.log(`Unknown gate action: ${gateAction}`);
|
|
2928
|
+
console.log('Use "pass" or "check"');
|
|
2929
|
+
process.exit(1);
|
|
2930
|
+
}
|
|
2931
|
+
} catch (err) {
|
|
2932
|
+
console.error(`Error: ${err.message}`);
|
|
2933
|
+
process.exit(1);
|
|
2934
|
+
}
|
|
2935
|
+
} else if (workflowSubcommand === '--help') {
|
|
2936
|
+
console.log('Usage: jettypod workflow <command>');
|
|
2937
|
+
console.log('');
|
|
2938
|
+
console.log('Commands:');
|
|
2939
|
+
console.log(' start <skill> <id> Start a skill for a work item (validates gates)');
|
|
2940
|
+
console.log(' complete <skill> <id> Complete a skill (marks gates passed)');
|
|
2941
|
+
console.log(' checkpoint <id> Update checkpoint progress');
|
|
2942
|
+
console.log(' gate <action> <id> Manage workflow gates');
|
|
2943
|
+
console.log(' resume Find interrupted workflows');
|
|
2944
|
+
console.log(' status <id> Show workflow status for a work item');
|
|
2945
|
+
console.log('');
|
|
2946
|
+
console.log('Legacy Commands:');
|
|
2947
|
+
console.log(' show <id> Show checkpoint details');
|
|
2948
|
+
console.log(' abort <id> Abort a checkpoint');
|
|
2583
2949
|
} else {
|
|
2584
2950
|
console.log('Usage: jettypod workflow <command>');
|
|
2585
2951
|
console.log('');
|
|
2586
2952
|
console.log('Commands:');
|
|
2587
|
-
console.log('
|
|
2588
|
-
console.log('
|
|
2589
|
-
console.log('
|
|
2590
|
-
console.log('
|
|
2953
|
+
console.log(' start <skill> <id> Start a skill for a work item (validates gates)');
|
|
2954
|
+
console.log(' complete <skill> <id> Complete a skill (marks gates passed)');
|
|
2955
|
+
console.log(' checkpoint <id> Update checkpoint progress');
|
|
2956
|
+
console.log(' gate <action> <id> Manage workflow gates');
|
|
2957
|
+
console.log(' resume Find interrupted workflows');
|
|
2958
|
+
console.log(' status <id> Show workflow status for a work item');
|
|
2591
2959
|
}
|
|
2592
2960
|
break;
|
|
2593
2961
|
}
|
package/lib/database.js
CHANGED
|
@@ -203,6 +203,39 @@ function initSchema() {
|
|
|
203
203
|
}
|
|
204
204
|
});
|
|
205
205
|
|
|
206
|
+
db.run(`
|
|
207
|
+
CREATE TABLE IF NOT EXISTS skill_executions (
|
|
208
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
209
|
+
work_item_id INTEGER NOT NULL,
|
|
210
|
+
skill_name TEXT NOT NULL,
|
|
211
|
+
status TEXT DEFAULT 'in_progress',
|
|
212
|
+
step_reached INTEGER DEFAULT 1,
|
|
213
|
+
context_json TEXT,
|
|
214
|
+
started_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
215
|
+
completed_at DATETIME,
|
|
216
|
+
FOREIGN KEY (work_item_id) REFERENCES work_items(id)
|
|
217
|
+
)
|
|
218
|
+
`, (err) => {
|
|
219
|
+
if (err) {
|
|
220
|
+
throw new Error(`Failed to create skill_executions table: ${err.message}`);
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
db.run(`
|
|
225
|
+
CREATE TABLE IF NOT EXISTS workflow_gates (
|
|
226
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
227
|
+
work_item_id INTEGER NOT NULL,
|
|
228
|
+
gate_name TEXT NOT NULL,
|
|
229
|
+
passed_at DATETIME,
|
|
230
|
+
UNIQUE(work_item_id, gate_name),
|
|
231
|
+
FOREIGN KEY (work_item_id) REFERENCES work_items(id)
|
|
232
|
+
)
|
|
233
|
+
`, (err) => {
|
|
234
|
+
if (err) {
|
|
235
|
+
throw new Error(`Failed to create workflow_gates table: ${err.message}`);
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
|
|
206
239
|
// Migrations: Add columns if they don't exist (for databases created before these columns were in base schema)
|
|
207
240
|
const handleAlterError = (columnName) => (err) => {
|
|
208
241
|
if (err && !err.message.includes('duplicate column')) {
|
|
@@ -1629,10 +1629,82 @@ async function mergeWork(options = {}) {
|
|
|
1629
1629
|
}
|
|
1630
1630
|
}
|
|
1631
1631
|
|
|
1632
|
+
/**
|
|
1633
|
+
* Create a worktree for writing BDD tests for a feature
|
|
1634
|
+
* @param {number} featureId - Feature ID to create test worktree for
|
|
1635
|
+
* @returns {Promise<Object>} Worktree info with path and branch
|
|
1636
|
+
* @throws {Error} If feature not found or not in correct phase
|
|
1637
|
+
*/
|
|
1638
|
+
async function testsWork(featureId) {
|
|
1639
|
+
if (!featureId || isNaN(featureId) || featureId < 1) {
|
|
1640
|
+
return Promise.reject(new Error('Invalid feature ID'));
|
|
1641
|
+
}
|
|
1642
|
+
|
|
1643
|
+
const paths = getPaths();
|
|
1644
|
+
|
|
1645
|
+
if (!fs.existsSync(paths.jettypodDir)) {
|
|
1646
|
+
return Promise.reject(new Error('JettyPod not initialized. Run: jettypod init'));
|
|
1647
|
+
}
|
|
1648
|
+
|
|
1649
|
+
if (!fs.existsSync(paths.dbPath)) {
|
|
1650
|
+
return Promise.reject(new Error('Work database not found. Run: jettypod init'));
|
|
1651
|
+
}
|
|
1652
|
+
|
|
1653
|
+
const db = getDb();
|
|
1654
|
+
|
|
1655
|
+
return new Promise((resolve, reject) => {
|
|
1656
|
+
// Get feature work item
|
|
1657
|
+
db.get(
|
|
1658
|
+
`SELECT * FROM work_items WHERE id = ? AND type = 'feature'`,
|
|
1659
|
+
[featureId],
|
|
1660
|
+
async (err, feature) => {
|
|
1661
|
+
if (err) {
|
|
1662
|
+
return reject(new Error(`Database error: ${err.message}`));
|
|
1663
|
+
}
|
|
1664
|
+
|
|
1665
|
+
if (!feature) {
|
|
1666
|
+
return reject(new Error(`Feature #${featureId} not found`));
|
|
1667
|
+
}
|
|
1668
|
+
|
|
1669
|
+
// Create worktree with tests/ prefix
|
|
1670
|
+
try {
|
|
1671
|
+
const gitRoot = getGitRoot();
|
|
1672
|
+
const worktreeResult = await worktreeManager.createWorktree(feature, {
|
|
1673
|
+
repoPath: gitRoot,
|
|
1674
|
+
branchPrefix: 'tests/feature',
|
|
1675
|
+
pathPrefix: 'tests-'
|
|
1676
|
+
});
|
|
1677
|
+
|
|
1678
|
+
// Set as current work
|
|
1679
|
+
setCurrentWork({
|
|
1680
|
+
id: feature.id,
|
|
1681
|
+
type: feature.type,
|
|
1682
|
+
title: feature.title,
|
|
1683
|
+
status: feature.status || 'in_progress',
|
|
1684
|
+
worktreePath: worktreeResult.worktree_path,
|
|
1685
|
+
branchName: worktreeResult.branch_name
|
|
1686
|
+
});
|
|
1687
|
+
|
|
1688
|
+
console.log(`✅ Created test worktree: ${worktreeResult.worktree_path}`);
|
|
1689
|
+
console.log(`📁 IMPORTANT: Use absolute paths for file operations:`);
|
|
1690
|
+
console.log(` ${worktreeResult.worktree_path}/features/your-feature.feature (correct)`);
|
|
1691
|
+
console.log(` features/your-feature.feature (wrong - creates in main repo)`);
|
|
1692
|
+
console.log(`\nWorking on tests for: [#${feature.id}] ${feature.title}`);
|
|
1693
|
+
|
|
1694
|
+
resolve(worktreeResult);
|
|
1695
|
+
} catch (worktreeErr) {
|
|
1696
|
+
reject(new Error(`Failed to create test worktree: ${worktreeErr.message}`));
|
|
1697
|
+
}
|
|
1698
|
+
}
|
|
1699
|
+
);
|
|
1700
|
+
});
|
|
1701
|
+
}
|
|
1702
|
+
|
|
1632
1703
|
module.exports = {
|
|
1633
1704
|
startWork,
|
|
1634
1705
|
stopWork,
|
|
1635
1706
|
getCurrentWork,
|
|
1636
1707
|
cleanupWorktrees,
|
|
1637
|
-
mergeWork
|
|
1708
|
+
mergeWork,
|
|
1709
|
+
testsWork
|
|
1638
1710
|
};
|
package/lib/worktree-manager.js
CHANGED
|
@@ -29,6 +29,8 @@ const VALID_STATUSES = ['active', 'merging', 'cleanup_pending', 'corrupted'];
|
|
|
29
29
|
* @param {Object} options - Optional configuration
|
|
30
30
|
* @param {string} options.repoPath - Path to git repository (defaults to process.cwd())
|
|
31
31
|
* @param {Object} options.db - Database connection (defaults to global database)
|
|
32
|
+
* @param {string} options.branchPrefix - Branch prefix (defaults to 'feature/work')
|
|
33
|
+
* @param {string} options.pathPrefix - Worktree path prefix (defaults to '')
|
|
32
34
|
* @returns {Promise<Object>} Worktree record with id, work_item_id, branch_name, worktree_path, status
|
|
33
35
|
*/
|
|
34
36
|
async function createWorktree(workItem, options = {}) {
|
|
@@ -85,9 +87,11 @@ async function createWorktree(workItem, options = {}) {
|
|
|
85
87
|
|
|
86
88
|
// Generate branch name and worktree path
|
|
87
89
|
const titleSlug = slugify(workItem.title || 'item');
|
|
88
|
-
const
|
|
90
|
+
const branchPrefix = options.branchPrefix || 'feature/work';
|
|
91
|
+
const pathPrefix = options.pathPrefix || '';
|
|
92
|
+
const branchName = `${branchPrefix}-${workItem.id}-${titleSlug}`;
|
|
89
93
|
const worktreeBasePath = path.join(gitRoot, '.jettypod-work');
|
|
90
|
-
const worktreePath = path.join(worktreeBasePath, `${workItem.id}-${titleSlug}`);
|
|
94
|
+
const worktreePath = path.join(worktreeBasePath, `${pathPrefix}${workItem.id}-${titleSlug}`);
|
|
91
95
|
|
|
92
96
|
let worktreeId = null;
|
|
93
97
|
let branchCreated = false;
|
package/package.json
CHANGED
|
@@ -13,20 +13,23 @@ When this skill is activated, you are helping discover the best approach for a f
|
|
|
13
13
|
|
|
14
14
|
## 🔑 Critical Command Distinction
|
|
15
15
|
|
|
16
|
-
**
|
|
16
|
+
**Four different commands, four different purposes:**
|
|
17
17
|
|
|
18
18
|
| Command | Used For | When | Phase |
|
|
19
19
|
|---------|----------|------|-------|
|
|
20
|
-
| `work implement <feature-id>` | Transition feature to implementation phase |
|
|
21
|
-
| `work start <
|
|
20
|
+
| `work implement <feature-id>` | Transition feature to implementation phase | After chores created (Step 8C) | Feature Planning |
|
|
21
|
+
| `work tests start <feature-id>` | Create worktree for test authoring | After transition (Step 8D) | Feature Planning |
|
|
22
|
+
| `work tests merge <feature-id>` | Merge tests to main, cleanup worktree | After tests validated (Step 8D) | Feature Planning |
|
|
23
|
+
| `work start <chore-id>` | Start implementing a specific chore | After tests merged (Step 8E) | Speed Mode |
|
|
22
24
|
|
|
23
|
-
**CRITICAL:**
|
|
24
|
-
- `work implement` = Ends feature planning,
|
|
25
|
-
- `work start` =
|
|
25
|
+
**CRITICAL:** All commands are run by **Claude**, not the user. The distinction is:
|
|
26
|
+
- `work implement` = Ends feature planning, transitions to implementation phase
|
|
27
|
+
- `work tests start/merge` = Write and land BDD tests in isolated worktree
|
|
28
|
+
- `work start` = Creates worktree/branch for chore implementation
|
|
26
29
|
|
|
27
|
-
**🛑 STOP GATE:** DO NOT run `work start` until Step
|
|
30
|
+
**🛑 STOP GATE:** DO NOT run `work start` until Step 8E. Tests must be merged to main first.
|
|
28
31
|
|
|
29
|
-
**🛑 HANDOFF REQUIREMENT:** After `work start`, you MUST invoke speed-mode using the Skill tool. See Step
|
|
32
|
+
**🛑 HANDOFF REQUIREMENT:** After `work start`, you MUST invoke speed-mode using the Skill tool. See Step 8E.
|
|
30
33
|
|
|
31
34
|
---
|
|
32
35
|
|
|
@@ -224,9 +227,9 @@ Example:
|
|
|
224
227
|
|
|
225
228
|
**WAIT for user to confirm or adjust the integration contract.**
|
|
226
229
|
|
|
227
|
-
#### Step 6B:
|
|
230
|
+
#### Step 6B: Propose BDD Scenarios (DO NOT WRITE YET)
|
|
228
231
|
|
|
229
|
-
**CRITICAL:**
|
|
232
|
+
**CRITICAL:** Do NOT write files yet. Display the proposed scenarios for user confirmation. Files will be written in Step 8D inside an isolated worktree.
|
|
230
233
|
|
|
231
234
|
**Feature slug:** Derive `FEATURE_SLUG` from the feature title:
|
|
232
235
|
- Lowercase
|
|
@@ -234,43 +237,79 @@ Example:
|
|
|
234
237
|
- Remove special characters
|
|
235
238
|
- Examples: "Email Password Login" → "email-password-login", "User's Profile (v2)" → "users-profile-v2"
|
|
236
239
|
|
|
237
|
-
Based on chosen approach,
|
|
240
|
+
Based on chosen approach, **display** the proposed scenarios to the user:
|
|
238
241
|
|
|
239
|
-
|
|
242
|
+
```
|
|
243
|
+
📋 Proposed BDD Scenarios
|
|
240
244
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
- All success paths - required functionality AND optional features
|
|
245
|
-
- Multiple valid workflows if the feature supports them
|
|
246
|
-
- Success variations (different outcomes that are all correct)
|
|
247
|
-
- NO error handling scenarios (added in stable mode)
|
|
248
|
-
- NO validation failures (added in stable mode)
|
|
249
|
-
- NO security/compliance scenarios (added in production mode)
|
|
245
|
+
**Feature file:** features/[feature-slug].feature
|
|
246
|
+
**Step definitions:** features/step_definitions/[feature-slug].steps.js
|
|
247
|
+
```
|
|
250
248
|
|
|
251
|
-
|
|
249
|
+
Then show the full Gherkin content:
|
|
252
250
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
4. **Include**:
|
|
257
|
-
- Test environment setup/cleanup
|
|
258
|
-
- All Given steps (setup state)
|
|
259
|
-
- All When steps (execute actions)
|
|
260
|
-
- All Then steps (verify outcomes)
|
|
251
|
+
```gherkin
|
|
252
|
+
Feature: [Feature Name]
|
|
253
|
+
[Brief description based on chosen UX approach]
|
|
261
254
|
|
|
262
|
-
|
|
255
|
+
Epic: [Epic name if applicable]
|
|
256
|
+
Approach: [Chosen approach name]
|
|
263
257
|
|
|
264
|
-
|
|
265
|
-
|
|
258
|
+
# Integration Contract:
|
|
259
|
+
# Entry Point: [from Step 6A]
|
|
260
|
+
# Caller Code: [from Step 6A]
|
|
261
|
+
|
|
262
|
+
# INTEGRATION SCENARIO (REQUIRED - proves feature is reachable)
|
|
263
|
+
Scenario: User can reach [feature] from [entry point]
|
|
264
|
+
Given I am on [existing entry point users can access]
|
|
265
|
+
When I [navigate/click/invoke to reach the feature]
|
|
266
|
+
Then I [see/access the feature]
|
|
267
|
+
|
|
268
|
+
# FEATURE SCENARIOS (required + optional functionality)
|
|
269
|
+
Scenario: [Core functionality - required workflow]
|
|
270
|
+
Given [initial state]
|
|
271
|
+
When [user takes main action]
|
|
272
|
+
Then [expected successful outcome]
|
|
273
|
+
And [observable UI/system state change]
|
|
274
|
+
|
|
275
|
+
Scenario: [Optional feature 1 - if applicable]
|
|
276
|
+
Given [initial state]
|
|
277
|
+
When [user uses optional feature]
|
|
278
|
+
Then [feature works correctly]
|
|
279
|
+
|
|
280
|
+
# SPEED MODE: All success scenarios above (integration + required + optional)
|
|
281
|
+
# STABLE MODE: Will add error handling, validation failures, edge cases
|
|
266
282
|
```
|
|
267
283
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
284
|
+
Then list the step definitions that will be created:
|
|
285
|
+
|
|
286
|
+
```
|
|
287
|
+
**Step definitions to implement:**
|
|
288
|
+
• Given I am on [entry point] - [brief implementation note]
|
|
289
|
+
• When I [action] - [brief implementation note]
|
|
290
|
+
• Then I [outcome] - [brief implementation note]
|
|
291
|
+
[etc.]
|
|
292
|
+
|
|
293
|
+
Does this capture the feature correctly? Any scenarios to add/change?
|
|
271
294
|
```
|
|
272
295
|
|
|
273
|
-
**
|
|
296
|
+
**Scenario content requirements:**
|
|
297
|
+
- **FIRST scenario MUST be the Integration Scenario** from Step 6A (proves reachability)
|
|
298
|
+
- **Include all "make it work" scenarios**:
|
|
299
|
+
- All success paths - required functionality AND optional features
|
|
300
|
+
- Multiple valid workflows if the feature supports them
|
|
301
|
+
- Success variations (different outcomes that are all correct)
|
|
302
|
+
- NO error handling scenarios (added in stable mode)
|
|
303
|
+
- NO validation failures (added in stable mode)
|
|
304
|
+
- NO security/compliance scenarios (added in production mode)
|
|
305
|
+
|
|
306
|
+
**WAIT for user confirmation.**
|
|
307
|
+
|
|
308
|
+
- If user confirms → Proceed to Step 7
|
|
309
|
+
- If user requests changes → Revise scenarios and display again
|
|
310
|
+
- **Store the confirmed scenarios in memory** - you'll write them in Step 8D
|
|
311
|
+
|
|
312
|
+
**Proceed to Step 7.**
|
|
274
313
|
|
|
275
314
|
**Template for speed mode (make it work):**
|
|
276
315
|
|
|
@@ -395,102 +434,9 @@ Scenario: Prevent unauthorized access
|
|
|
395
434
|
|
|
396
435
|
</details>
|
|
397
436
|
|
|
398
|
-
### Step 6.5: Validate BDD Infrastructure
|
|
399
|
-
|
|
400
|
-
**CRITICAL:** After creating both scenario and step definition files, validate them with cucumber dry-run to catch errors immediately.
|
|
401
|
-
|
|
402
|
-
**Run this command** (replace `${FEATURE_SLUG}` with actual slug):
|
|
403
|
-
|
|
404
|
-
```bash
|
|
405
|
-
npx cucumber-js --dry-run features/${FEATURE_SLUG}.feature
|
|
406
|
-
```
|
|
407
|
-
|
|
408
|
-
Example:
|
|
409
|
-
```bash
|
|
410
|
-
npx cucumber-js --dry-run features/email-password-login.feature
|
|
411
|
-
```
|
|
412
|
-
|
|
413
|
-
**What the output means:**
|
|
414
|
-
|
|
415
|
-
✅ **Success** - No errors, all steps have definitions:
|
|
416
|
-
```
|
|
417
|
-
0 scenarios
|
|
418
|
-
0 steps
|
|
419
|
-
```
|
|
420
|
-
(Dry-run doesn't execute, so 0 is correct)
|
|
421
|
-
|
|
422
|
-
❌ **Undefined steps** - Missing step definitions:
|
|
423
|
-
```
|
|
424
|
-
Undefined. Implement with the following snippet:
|
|
425
|
-
Given('I am on the login page', function () {
|
|
426
|
-
...
|
|
427
|
-
});
|
|
428
|
-
```
|
|
429
|
-
→ Add the missing step definition to your `.steps.js` file
|
|
430
|
-
|
|
431
|
-
❌ **Syntax error** - Invalid Gherkin:
|
|
432
|
-
```
|
|
433
|
-
Parse error in features/foo.feature
|
|
434
|
-
```
|
|
435
|
-
→ Fix the feature file syntax
|
|
436
|
-
|
|
437
|
-
❌ **Duplicate steps** - Multiple definitions match:
|
|
438
|
-
```
|
|
439
|
-
Multiple step definitions match
|
|
440
|
-
```
|
|
441
|
-
→ Remove or rename one of the duplicate step definitions
|
|
442
|
-
|
|
443
|
-
**If validation fails:**
|
|
444
|
-
1. Read the error message carefully
|
|
445
|
-
2. Fix the step definitions or scenario file
|
|
446
|
-
3. Re-run the dry-run command
|
|
447
|
-
4. **Loop until validation passes** - do NOT proceed to Step 7 until green
|
|
448
|
-
|
|
449
|
-
**If validation succeeds:**
|
|
450
|
-
Display: "✅ BDD infrastructure validated - all steps have definitions"
|
|
451
|
-
|
|
452
|
-
**Proceed to Step 6.6.**
|
|
453
|
-
|
|
454
|
-
**Note:** Unit tests are created during speed mode implementation, not during feature planning. Feature planning focuses on BDD scenarios (user experience); speed mode handles unit tests (implementation details).
|
|
455
|
-
|
|
456
|
-
### Step 6.6: Commit BDD Files to Main
|
|
457
|
-
|
|
458
|
-
**CRITICAL:** The BDD files must be committed to main BEFORE creating worktrees. If you skip this step, worktrees won't have the scenario files and speed-mode will fail.
|
|
459
|
-
|
|
460
|
-
**Commit the BDD files:**
|
|
461
|
-
|
|
462
|
-
```bash
|
|
463
|
-
git add features/${FEATURE_SLUG}.feature features/steps/${FEATURE_SLUG}.* features/step_definitions/${FEATURE_SLUG}.*
|
|
464
|
-
git commit -m "test: Add BDD scenarios for ${FEATURE_TITLE}
|
|
465
|
-
|
|
466
|
-
- Feature file: features/${FEATURE_SLUG}.feature
|
|
467
|
-
- Step definitions for speed mode scenarios"
|
|
468
|
-
|
|
469
|
-
git push
|
|
470
|
-
```
|
|
471
|
-
|
|
472
|
-
Replace `${FEATURE_SLUG}` and `${FEATURE_TITLE}` with actual values. Example:
|
|
473
|
-
```bash
|
|
474
|
-
git add features/email-password-login.feature features/steps/email-password-login.* features/step_definitions/email-password-login.*
|
|
475
|
-
git commit -m "test: Add BDD scenarios for Email Password Login
|
|
476
|
-
|
|
477
|
-
- Feature file: features/email-password-login.feature
|
|
478
|
-
- Step definitions for speed mode scenarios"
|
|
479
|
-
|
|
480
|
-
git push
|
|
481
|
-
```
|
|
482
|
-
|
|
483
|
-
**Why this matters:**
|
|
484
|
-
- Worktrees are created from the current branch (usually main)
|
|
485
|
-
- If BDD files aren't committed, they won't exist in the worktree
|
|
486
|
-
- Speed-mode will fail trying to run tests against non-existent files
|
|
487
|
-
- **Push is required** so other assistants/machines have the latest main
|
|
488
|
-
|
|
489
|
-
**Proceed to Step 7.**
|
|
490
|
-
|
|
491
437
|
### Step 7: Propose Speed Mode Chores
|
|
492
438
|
|
|
493
|
-
**CRITICAL:** After BDD
|
|
439
|
+
**CRITICAL:** After user confirms the BDD scenarios, analyze the codebase and propose technical implementation chores. **DO NOT CREATE CHORES YET** - the feature must transition to implementation mode first (Step 8).
|
|
494
440
|
|
|
495
441
|
**Your analysis should consider:**
|
|
496
442
|
- The BDD scenarios (integration + all success paths)
|
|
@@ -633,6 +579,139 @@ After successful transition, display:
|
|
|
633
579
|
✅ Feature transitioned to implementation phase
|
|
634
580
|
✅ Created X chores for speed mode
|
|
635
581
|
|
|
582
|
+
Now I'll write the BDD tests in an isolated worktree...
|
|
583
|
+
```
|
|
584
|
+
|
|
585
|
+
**Proceed to Step 8D.**
|
|
586
|
+
|
|
587
|
+
#### Step 8D: Write Tests in Isolated Worktree
|
|
588
|
+
|
|
589
|
+
**CRITICAL:** Tests are written in an isolated worktree, not directly on main. This ensures agent failures don't leave uncommitted garbage on main.
|
|
590
|
+
|
|
591
|
+
**Sub-step 1: Create test worktree**
|
|
592
|
+
|
|
593
|
+
```bash
|
|
594
|
+
jettypod work tests start ${FEATURE_ID}
|
|
595
|
+
```
|
|
596
|
+
|
|
597
|
+
This creates:
|
|
598
|
+
- Worktree at `.jettypod-work/tests-<id>-<slug>/`
|
|
599
|
+
- Branch `tests/feature-<id>-<slug>`
|
|
600
|
+
|
|
601
|
+
**Capture the worktree path from output** - you'll need it for file operations.
|
|
602
|
+
|
|
603
|
+
Example output:
|
|
604
|
+
```
|
|
605
|
+
✅ Created test worktree: /path/to/.jettypod-work/tests-42-email-login
|
|
606
|
+
Branch: tests/feature-42-email-login
|
|
607
|
+
|
|
608
|
+
Write your BDD files to:
|
|
609
|
+
<worktree>/features/email-login.feature
|
|
610
|
+
<worktree>/features/step_definitions/email-login.steps.js
|
|
611
|
+
|
|
612
|
+
When done, run: jettypod work tests merge 42
|
|
613
|
+
```
|
|
614
|
+
|
|
615
|
+
**🛑 STOP AND CHECK:** Verify worktree was created successfully. If you see an error, investigate before continuing.
|
|
616
|
+
|
|
617
|
+
**Sub-step 2: Write BDD files in the worktree**
|
|
618
|
+
|
|
619
|
+
Using the scenarios you confirmed in Step 6B, write the files using **absolute paths to the worktree**:
|
|
620
|
+
|
|
621
|
+
**A. Write scenario file** using Write tool:
|
|
622
|
+
```
|
|
623
|
+
<worktree_path>/features/${FEATURE_SLUG}.feature
|
|
624
|
+
```
|
|
625
|
+
|
|
626
|
+
**B. Write step definitions file** using Write tool:
|
|
627
|
+
```
|
|
628
|
+
<worktree_path>/features/step_definitions/${FEATURE_SLUG}.steps.js
|
|
629
|
+
```
|
|
630
|
+
|
|
631
|
+
**Step definition requirements:**
|
|
632
|
+
1. Implement all Given/When/Then steps from the scenarios
|
|
633
|
+
2. Follow existing patterns - check other `.steps.js` files for conventions
|
|
634
|
+
3. Include test environment setup/cleanup
|
|
635
|
+
4. All Given steps (setup state)
|
|
636
|
+
5. All When steps (execute actions)
|
|
637
|
+
6. All Then steps (verify outcomes)
|
|
638
|
+
|
|
639
|
+
**Sub-step 3: Validate BDD infrastructure**
|
|
640
|
+
|
|
641
|
+
Run cucumber dry-run **from within the worktree**:
|
|
642
|
+
|
|
643
|
+
```bash
|
|
644
|
+
cd <worktree_path> && npx cucumber-js --dry-run features/${FEATURE_SLUG}.feature
|
|
645
|
+
```
|
|
646
|
+
|
|
647
|
+
**What the output means:**
|
|
648
|
+
|
|
649
|
+
✅ **Success** - No errors, all steps have definitions:
|
|
650
|
+
```
|
|
651
|
+
0 scenarios
|
|
652
|
+
0 steps
|
|
653
|
+
```
|
|
654
|
+
(Dry-run doesn't execute, so 0 is correct)
|
|
655
|
+
|
|
656
|
+
❌ **Undefined steps** - Missing step definitions:
|
|
657
|
+
```
|
|
658
|
+
Undefined. Implement with the following snippet:
|
|
659
|
+
Given('I am on the login page', function () {
|
|
660
|
+
...
|
|
661
|
+
});
|
|
662
|
+
```
|
|
663
|
+
→ Add the missing step definition to your `.steps.js` file
|
|
664
|
+
|
|
665
|
+
❌ **Syntax error** - Invalid Gherkin:
|
|
666
|
+
```
|
|
667
|
+
Parse error in features/foo.feature
|
|
668
|
+
```
|
|
669
|
+
→ Fix the feature file syntax
|
|
670
|
+
|
|
671
|
+
❌ **Duplicate steps** - Multiple definitions match:
|
|
672
|
+
```
|
|
673
|
+
Multiple step definitions match
|
|
674
|
+
```
|
|
675
|
+
→ Remove or rename one of the duplicate step definitions
|
|
676
|
+
|
|
677
|
+
**If validation fails:**
|
|
678
|
+
1. Read the error message carefully
|
|
679
|
+
2. Fix the step definitions or scenario file **in the worktree**
|
|
680
|
+
3. Re-run the dry-run command
|
|
681
|
+
4. **Loop until validation passes** - do NOT proceed until green
|
|
682
|
+
|
|
683
|
+
**If validation succeeds:**
|
|
684
|
+
Display: "✅ BDD infrastructure validated - all steps have definitions"
|
|
685
|
+
|
|
686
|
+
**Sub-step 4: Update database with scenario file path**
|
|
687
|
+
|
|
688
|
+
```bash
|
|
689
|
+
sqlite3 .jettypod/work.db "UPDATE work_items SET scenario_file = 'features/${FEATURE_SLUG}.feature' WHERE id = ${FEATURE_ID}"
|
|
690
|
+
```
|
|
691
|
+
|
|
692
|
+
**Sub-step 5: Merge tests to main**
|
|
693
|
+
|
|
694
|
+
```bash
|
|
695
|
+
jettypod work tests merge ${FEATURE_ID}
|
|
696
|
+
```
|
|
697
|
+
|
|
698
|
+
This will:
|
|
699
|
+
- Commit changes in the worktree
|
|
700
|
+
- Merge to main
|
|
701
|
+
- Push to remote
|
|
702
|
+
- Clean up the worktree
|
|
703
|
+
|
|
704
|
+
**🛑 STOP AND CHECK:** Verify merge succeeded:
|
|
705
|
+
- ✅ "Tests merged to main" → Proceed to Step 8E
|
|
706
|
+
- ❌ Error → Investigate, worktree still exists for debugging
|
|
707
|
+
|
|
708
|
+
**After successful merge, display:**
|
|
709
|
+
|
|
710
|
+
```
|
|
711
|
+
✅ Feature transitioned to implementation phase
|
|
712
|
+
✅ Created X chores for speed mode
|
|
713
|
+
✅ BDD tests merged to main
|
|
714
|
+
|
|
636
715
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
637
716
|
🎯 Feature Planning Complete!
|
|
638
717
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
@@ -648,9 +727,11 @@ After successful transition, display:
|
|
|
648
727
|
Start this one? [yes / pick different / done for now]
|
|
649
728
|
```
|
|
650
729
|
|
|
651
|
-
|
|
730
|
+
**Proceed to Step 8E.**
|
|
731
|
+
|
|
732
|
+
#### Step 8E: Start First Chore
|
|
652
733
|
|
|
653
|
-
**Feature planning is now complete.** The feature has been transitioned to implementation phase and chores are ready.
|
|
734
|
+
**Feature planning is now complete.** The feature has been transitioned to implementation phase, tests are on main, and chores are ready.
|
|
654
735
|
|
|
655
736
|
The completion message above already includes your recommendation. Now:
|
|
656
737
|
|
|
@@ -763,14 +844,15 @@ Before completing feature planning, ensure:
|
|
|
763
844
|
- [ ] Exactly 3 approaches suggested
|
|
764
845
|
- [ ] Winner chosen (with prototypes or without)
|
|
765
846
|
- [ ] **Integration Contract defined** (entry point, caller code, integration scenario)
|
|
766
|
-
- [ ] **Integration Scenario is FIRST scenario** in
|
|
767
|
-
- [ ] BDD scenarios
|
|
768
|
-
- [ ]
|
|
769
|
-
- [ ]
|
|
770
|
-
- [ ]
|
|
771
|
-
- [ ]
|
|
772
|
-
- [ ]
|
|
773
|
-
- [ ] **
|
|
774
|
-
- [ ]
|
|
775
|
-
- [ ]
|
|
776
|
-
- [ ]
|
|
847
|
+
- [ ] **Integration Scenario is FIRST scenario** in proposed scenarios
|
|
848
|
+
- [ ] BDD scenarios **proposed and confirmed** by user (Step 6B)
|
|
849
|
+
- [ ] **At least one Integration Chore proposed** (wires feature into app)
|
|
850
|
+
- [ ] Speed mode chores proposed and confirmed (Step 7)
|
|
851
|
+
- [ ] Chores created in database (Step 8B)
|
|
852
|
+
- [ ] Feature transitioned to implementation with `work implement` (Step 8C)
|
|
853
|
+
- [ ] **Test worktree created** with `work tests start` (Step 8D)
|
|
854
|
+
- [ ] **BDD files written** in worktree (Step 8D)
|
|
855
|
+
- [ ] **BDD infrastructure validated** with dry-run (Step 8D)
|
|
856
|
+
- [ ] **Tests merged to main** with `work tests merge` (Step 8D)
|
|
857
|
+
- [ ] First chore started with `work start [chore-id]` (Step 8E)
|
|
858
|
+
- [ ] **Speed-mode skill invoked using Skill tool** (Step 8E)
|
|
@@ -627,22 +627,34 @@ After merge, you are on main branch. Ready to generate stable mode scenarios.
|
|
|
627
627
|
|
|
628
628
|
**⚡ ASYNC BOUNDARY - Must wait for user confirmation**
|
|
629
629
|
|
|
630
|
-
**1.
|
|
630
|
+
**1. Create a test worktree for writing stable mode scenarios:**
|
|
631
|
+
|
|
632
|
+
```bash
|
|
633
|
+
jettypod work tests <feature-id>
|
|
634
|
+
```
|
|
635
|
+
|
|
636
|
+
This creates an isolated worktree at `.jettypod-work/tests-<id>-<slug>` with branch `tests/feature-<id>-<slug>`.
|
|
637
|
+
|
|
638
|
+
**🛑 CRITICAL:** All BDD file operations MUST use absolute paths in the test worktree:
|
|
639
|
+
- ✅ `/path/to/.jettypod-work/tests-42-login/features/login.feature`
|
|
640
|
+
- ❌ `features/login.feature` (this would write to main repo!)
|
|
641
|
+
|
|
642
|
+
**2. Read the feature's scenario file and analyze for stable mode needs:**
|
|
631
643
|
|
|
632
644
|
```bash
|
|
633
645
|
sqlite3 .jettypod/work.db "SELECT id, title, scenario_file FROM work_items WHERE id = <feature-id>"
|
|
634
646
|
```
|
|
635
647
|
|
|
636
|
-
**
|
|
648
|
+
**3. Generate stable mode BDD scenarios** covering:
|
|
637
649
|
- **Error scenarios** - Invalid input, missing params, system errors, permission failures
|
|
638
650
|
- **Edge cases** - Empty inputs, boundary values, special characters
|
|
639
651
|
- **State consistency** - Concurrent ops, partial failures, invalid state transitions
|
|
640
652
|
|
|
641
|
-
**
|
|
653
|
+
**4. Append scenarios to the feature file IN THE TEST WORKTREE:**
|
|
642
654
|
|
|
643
|
-
|
|
644
|
-
cat >> <scenario-file-path> << 'EOF'
|
|
655
|
+
Use the Edit tool to append to `<worktree-path>/features/<feature-file>`:
|
|
645
656
|
|
|
657
|
+
```gherkin
|
|
646
658
|
# Stable Mode Scenarios - Error Handling and Edge Cases
|
|
647
659
|
|
|
648
660
|
Scenario: [Error scenario title]
|
|
@@ -654,14 +666,15 @@ Scenario: [Edge case title]
|
|
|
654
666
|
Given [context]
|
|
655
667
|
When [edge case input]
|
|
656
668
|
Then [expected behavior]
|
|
657
|
-
EOF
|
|
658
669
|
```
|
|
659
670
|
|
|
660
|
-
**
|
|
671
|
+
**5. Create step definitions IN THE TEST WORKTREE** at `<worktree-path>/features/step_definitions/<feature-slug>-stable.steps.js`
|
|
661
672
|
|
|
662
|
-
**
|
|
673
|
+
**6. Commit and merge the test worktree:**
|
|
663
674
|
|
|
664
675
|
```bash
|
|
676
|
+
# Commit in the test worktree
|
|
677
|
+
cd <worktree-path>
|
|
665
678
|
git add features/
|
|
666
679
|
git commit -m "test: Add stable mode BDD scenarios and step definitions
|
|
667
680
|
|
|
@@ -669,13 +682,12 @@ Added error handling and edge case scenarios for stable mode.
|
|
|
669
682
|
- [N] new stable mode scenarios
|
|
670
683
|
- Step definitions for validation and error handling"
|
|
671
684
|
|
|
672
|
-
|
|
685
|
+
# Return to main repo and merge
|
|
686
|
+
cd <main-repo-path>
|
|
687
|
+
jettypod work merge <feature-id>
|
|
673
688
|
```
|
|
674
689
|
|
|
675
|
-
|
|
676
|
-
Other assistants may be working in parallel - uncommitted files will block their merges.
|
|
677
|
-
|
|
678
|
-
**6. Present proposal to user:**
|
|
690
|
+
**7. Present proposal to user:**
|
|
679
691
|
|
|
680
692
|
```
|
|
681
693
|
📋 Stable Mode Transition
|
|
@@ -826,6 +838,12 @@ jettypod work merge --release-lock # Release held lock
|
|
|
826
838
|
jettypod work start <chore-id> # Create worktree and start chore
|
|
827
839
|
```
|
|
828
840
|
|
|
841
|
+
**Create test worktree (for writing BDD scenarios):**
|
|
842
|
+
```bash
|
|
843
|
+
jettypod work tests <feature-id> # Create worktree for writing tests
|
|
844
|
+
jettypod work merge <feature-id> # Merge tests back to main
|
|
845
|
+
```
|
|
846
|
+
|
|
829
847
|
**Set feature mode (BEFORE creating chores):**
|
|
830
848
|
```bash
|
|
831
849
|
jettypod work set-mode <feature-id> stable
|