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 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
- const checkpoints = await new Promise((resolve, reject) => {
2315
- db.all(
2316
- `SELECT * FROM workflow_checkpoints ORDER BY updated_at DESC`,
2317
- [],
2318
- (err, rows) => {
2319
- if (err) reject(err);
2320
- else resolve(rows || []);
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
- 'ID'.padEnd(4) +
2333
- 'Skill'.padEnd(18) +
2334
- 'Step'.padEnd(10) +
2335
- 'Work Item'.padEnd(12) +
2336
- 'Branch'.padEnd(38) +
2337
- 'Last Active'
2338
- );
2339
- console.log('─'.repeat(90));
2340
-
2341
- for (const cp of checkpoints) {
2342
- const stepInfo = cp.total_steps
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
- String(cp.id).padEnd(4) +
2361
- cp.skill_name.padEnd(18) +
2362
- stepInfo.padEnd(10) +
2363
- `#${cp.work_item_id || '-'}`.padEnd(12) +
2364
- branchWithMarker.padEnd(38) +
2365
- timeAgo
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
- console.log('');
2370
- console.log(`${checkpoints.length} interrupted workflow(s)`);
2371
- console.log('');
2372
- console.log('Commands:');
2373
- console.log(' jettypod workflow show <id> Show workflow details');
2374
- console.log(' jettypod workflow resume <id> Resume a workflow');
2375
- console.log(' jettypod workflow abort <id> Abort and clean up a workflow');
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
- let checkpoint;
2448
-
2449
- if (workflowId) {
2450
- // Resume by ID - fetch specific workflow
2451
- checkpoint = await new Promise((resolve, reject) => {
2452
- db.get(
2453
- `SELECT * FROM workflow_checkpoints WHERE id = ?`,
2454
- [workflowId],
2455
- (err, row) => {
2456
- if (err) reject(err);
2457
- else resolve(row);
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
- if (!checkpoint) {
2463
- console.log(`Error: No workflow found with ID ${workflowId}`);
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
- // No ID - check current branch
2470
- checkpoint = await getCheckpoint(db, currentBranch);
2471
-
2472
- if (!checkpoint) {
2473
- console.log('No interrupted workflow on current branch.');
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(' status List all interrupted workflows');
2588
- console.log(' show <id> Show workflow details');
2589
- console.log(' resume [id] Resume a workflow');
2590
- console.log(' abort <id> Abort and clean up a workflow');
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
  }