jettypod 4.4.45 → 4.4.47
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/chore-mode/SKILL.md +8 -0
- package/skills-templates/feature-planning/SKILL.md +242 -144
- package/skills-templates/speed-mode/SKILL.md +39 -13
- package/skills-templates/stable-mode/SKILL.md +8 -0
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
|
}
|