aidp 0.15.2 → 0.16.0

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.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +47 -0
  3. data/lib/aidp/analyze/error_handler.rb +14 -15
  4. data/lib/aidp/analyze/runner.rb +27 -5
  5. data/lib/aidp/analyze/steps.rb +4 -0
  6. data/lib/aidp/cli/jobs_command.rb +2 -1
  7. data/lib/aidp/cli.rb +812 -3
  8. data/lib/aidp/concurrency/backoff.rb +148 -0
  9. data/lib/aidp/concurrency/exec.rb +192 -0
  10. data/lib/aidp/concurrency/wait.rb +148 -0
  11. data/lib/aidp/concurrency.rb +71 -0
  12. data/lib/aidp/config.rb +20 -0
  13. data/lib/aidp/daemon/runner.rb +9 -8
  14. data/lib/aidp/debug_mixin.rb +1 -0
  15. data/lib/aidp/errors.rb +12 -0
  16. data/lib/aidp/execute/interactive_repl.rb +102 -11
  17. data/lib/aidp/execute/repl_macros.rb +776 -2
  18. data/lib/aidp/execute/runner.rb +27 -5
  19. data/lib/aidp/execute/steps.rb +2 -0
  20. data/lib/aidp/harness/config_loader.rb +24 -2
  21. data/lib/aidp/harness/enhanced_runner.rb +16 -2
  22. data/lib/aidp/harness/error_handler.rb +1 -1
  23. data/lib/aidp/harness/provider_info.rb +19 -15
  24. data/lib/aidp/harness/provider_manager.rb +47 -41
  25. data/lib/aidp/harness/runner.rb +3 -11
  26. data/lib/aidp/harness/state/persistence.rb +1 -6
  27. data/lib/aidp/harness/state_manager.rb +115 -7
  28. data/lib/aidp/harness/status_display.rb +11 -18
  29. data/lib/aidp/harness/ui/navigation/submenu.rb +1 -0
  30. data/lib/aidp/harness/ui/workflow_controller.rb +1 -1
  31. data/lib/aidp/harness/user_interface.rb +12 -15
  32. data/lib/aidp/jobs/background_runner.rb +15 -5
  33. data/lib/aidp/providers/codex.rb +0 -1
  34. data/lib/aidp/providers/cursor.rb +0 -1
  35. data/lib/aidp/providers/github_copilot.rb +0 -1
  36. data/lib/aidp/providers/opencode.rb +0 -1
  37. data/lib/aidp/skills/composer.rb +178 -0
  38. data/lib/aidp/skills/loader.rb +205 -0
  39. data/lib/aidp/skills/registry.rb +220 -0
  40. data/lib/aidp/skills/skill.rb +174 -0
  41. data/lib/aidp/skills.rb +30 -0
  42. data/lib/aidp/version.rb +1 -1
  43. data/lib/aidp/watch/build_processor.rb +93 -28
  44. data/lib/aidp/watch/runner.rb +3 -2
  45. data/lib/aidp/workstream_executor.rb +244 -0
  46. data/lib/aidp/workstream_state.rb +212 -0
  47. data/lib/aidp/worktree.rb +208 -0
  48. data/lib/aidp.rb +6 -0
  49. metadata +17 -4
data/lib/aidp/cli.rb CHANGED
@@ -9,6 +9,7 @@ require_relative "harness/ui/enhanced_workflow_selector"
9
9
  require_relative "harness/enhanced_runner"
10
10
  require_relative "cli/first_run_wizard"
11
11
  require_relative "rescue_logging"
12
+ require_relative "concurrency"
12
13
 
13
14
  module Aidp
14
15
  # CLI interface for AIDP
@@ -286,6 +287,27 @@ module Aidp
286
287
  opts.separator " mcp MCP server dashboard and management"
287
288
  opts.separator " dashboard - Show all MCP servers across providers"
288
289
  opts.separator " check <servers...> - Check provider eligibility for servers"
290
+ opts.separator " ws Manage parallel workstreams (git worktrees)"
291
+ opts.separator " list - List all workstreams"
292
+ opts.separator " new <slug> [task] - Create new workstream"
293
+ opts.separator " rm <slug> - Remove workstream"
294
+ opts.separator " status <slug> - Show workstream status"
295
+ opts.separator " pause <slug> - Pause workstream"
296
+ opts.separator " resume <slug> - Resume workstream"
297
+ opts.separator " complete <slug> - Mark workstream as completed"
298
+ opts.separator " dashboard - Show multi-workstream overview"
299
+ opts.separator " pause-all - Pause all active workstreams"
300
+ opts.separator " resume-all - Resume all paused workstreams"
301
+ opts.separator " stop-all - Stop all active workstreams"
302
+ opts.separator " work Execute workflow in workstream context"
303
+ opts.separator " --workstream <slug> - Required: workstream to run in"
304
+ opts.separator " --mode <mode> - analyze or execute (default: execute)"
305
+ opts.separator " --background - Run in background job"
306
+ opts.separator " skill Manage skills (agent personas)"
307
+ opts.separator " list - List all available skills"
308
+ opts.separator " show <id> - Show detailed skill information"
309
+ opts.separator " search <query> - Search skills by keyword"
310
+ opts.separator " validate [path] - Validate skill file format"
289
311
  opts.separator " harness Manage harness state"
290
312
  opts.separator " config Manage configuration"
291
313
  opts.separator " status - Show harness status"
@@ -338,7 +360,7 @@ module Aidp
338
360
  # Determine if the invocation is a subcommand style call
339
361
  def subcommand?(args)
340
362
  return false if args.nil? || args.empty?
341
- %w[status jobs kb harness providers checkpoint mcp issue config init watch].include?(args.first)
363
+ %w[status jobs kb harness providers checkpoint mcp issue config init watch ws work skill].include?(args.first)
342
364
  end
343
365
 
344
366
  def run_subcommand(args)
@@ -355,6 +377,9 @@ module Aidp
355
377
  when "config" then run_config_command(args)
356
378
  when "init" then run_init_command(args)
357
379
  when "watch" then run_watch_command(args)
380
+ when "ws" then run_ws_command(args)
381
+ when "work" then run_work_command(args)
382
+ when "skill" then run_skill_command(args)
358
383
  else
359
384
  display_message("Unknown command: #{cmd}", type: :info)
360
385
  return 1
@@ -461,7 +486,15 @@ module Aidp
461
486
  if follow
462
487
  display_message("", type: :info)
463
488
  display_message("Following logs (Ctrl+C to stop following)...", type: :info)
464
- sleep 1 # Give daemon time to start writing logs
489
+
490
+ # Wait for log file to be created before following
491
+ log_file = File.join(runner.instance_variable_get(:@jobs_dir), job_id, "output.log")
492
+ begin
493
+ Aidp::Concurrency::Wait.for_file(log_file, timeout: 10, interval: 0.2)
494
+ rescue Aidp::Concurrency::TimeoutError
495
+ display_message("Warning: Log file not found after 10s", type: :warning)
496
+ end
497
+
465
498
  runner.follow_job_logs(job_id)
466
499
  end
467
500
 
@@ -1060,7 +1093,7 @@ module Aidp
1060
1093
 
1061
1094
  def run_watch_command(args)
1062
1095
  if args.empty?
1063
- display_message("Usage: aidp watch <issues_url> [--interval SECONDS] [--provider NAME] [--once]", type: :info)
1096
+ display_message("Usage: aidp watch <issues_url> [--interval SECONDS] [--provider NAME] [--once] [--no-workstreams]", type: :info)
1064
1097
  return
1065
1098
  end
1066
1099
 
@@ -1068,6 +1101,7 @@ module Aidp
1068
1101
  interval = Aidp::Watch::Runner::DEFAULT_INTERVAL
1069
1102
  provider_name = nil
1070
1103
  once = false
1104
+ use_workstreams = true # Default to using workstreams
1071
1105
 
1072
1106
  until args.empty?
1073
1107
  token = args.shift
@@ -1079,6 +1113,8 @@ module Aidp
1079
1113
  provider_name = args.shift
1080
1114
  when "--once"
1081
1115
  once = true
1116
+ when "--no-workstreams"
1117
+ use_workstreams = false
1082
1118
  else
1083
1119
  display_message("⚠️ Unknown watch option: #{token}", type: :warn)
1084
1120
  end
@@ -1090,6 +1126,7 @@ module Aidp
1090
1126
  provider_name: provider_name,
1091
1127
  project_dir: Dir.pwd,
1092
1128
  once: once,
1129
+ use_workstreams: use_workstreams,
1093
1130
  prompt: TTY::Prompt.new
1094
1131
  )
1095
1132
  runner.start
@@ -1098,9 +1135,781 @@ module Aidp
1098
1135
  display_message("❌ #{e.message}", type: :error)
1099
1136
  end
1100
1137
 
1138
+ def run_ws_command(args)
1139
+ require_relative "worktree"
1140
+ require_relative "workstream_state"
1141
+ require "tty-table"
1142
+
1143
+ subcommand = args.shift
1144
+
1145
+ case subcommand
1146
+ when "list", nil
1147
+ # List all workstreams
1148
+ workstreams = Aidp::Worktree.list(project_dir: Dir.pwd)
1149
+
1150
+ if workstreams.empty?
1151
+ display_message("No workstreams found.", type: :info)
1152
+ display_message("Create one with: aidp ws new <slug> [task]", type: :muted)
1153
+ return
1154
+ end
1155
+
1156
+ display_message("Workstreams", type: :highlight)
1157
+ display_message("=" * 80, type: :muted)
1158
+
1159
+ table_rows = workstreams.map do |ws|
1160
+ state = Aidp::WorkstreamState.read(slug: ws[:slug], project_dir: Dir.pwd) || {}
1161
+ status_icon = ws[:active] ? "✓" : "✗"
1162
+ created = Time.parse(ws[:created_at]).strftime("%Y-%m-%d %H:%M")
1163
+ iterations = state[:iterations] || 0
1164
+ task = state[:task] && state[:task].to_s[0, 40]
1165
+ [
1166
+ status_icon,
1167
+ ws[:slug],
1168
+ ws[:branch],
1169
+ created,
1170
+ ws[:active] ? "active" : "inactive",
1171
+ iterations,
1172
+ task
1173
+ ]
1174
+ end
1175
+
1176
+ header = ["", "Slug", "Branch", "Created", "Status", "Iter", "Task"]
1177
+ table = TTY::Table.new(header, table_rows)
1178
+ # Render with explicit width for non-TTY environments (e.g., tests)
1179
+ renderer = if $stdout.tty?
1180
+ table.render(:basic)
1181
+ else
1182
+ table.render(:basic, width: 120)
1183
+ end
1184
+ display_message(renderer, type: :info)
1185
+
1186
+ when "new"
1187
+ # Create new workstream
1188
+ slug = args.shift
1189
+ unless slug
1190
+ display_message("❌ Missing slug", type: :error)
1191
+ display_message("Usage: aidp ws new <slug> [task]", type: :info)
1192
+ return
1193
+ end
1194
+
1195
+ # Validate slug format (lowercase, hyphens, no special chars)
1196
+ unless slug.match?(/^[a-z0-9]+(-[a-z0-9]+)*$/)
1197
+ display_message("❌ Invalid slug format", type: :error)
1198
+ display_message(" Slug must be lowercase with hyphens (e.g., 'issue-123-fix-auth')", type: :info)
1199
+ return
1200
+ end
1201
+
1202
+ task_parts = []
1203
+ base_branch = nil
1204
+ until args.empty?
1205
+ token = args.shift
1206
+ if token == "--base-branch"
1207
+ base_branch = args.shift
1208
+ else
1209
+ task_parts << token
1210
+ end
1211
+ end
1212
+ task = task_parts.join(" ")
1213
+
1214
+ begin
1215
+ result = Aidp::Worktree.create(
1216
+ slug: slug,
1217
+ project_dir: Dir.pwd,
1218
+ base_branch: base_branch,
1219
+ task: (task unless task.empty?)
1220
+ )
1221
+
1222
+ display_message("✓ Created workstream: #{slug}", type: :success)
1223
+ display_message(" Path: #{result[:path]}", type: :info)
1224
+ display_message(" Branch: #{result[:branch]}", type: :info)
1225
+ display_message(" Task: #{task}", type: :info) unless task.empty?
1226
+ display_message("", type: :info)
1227
+ display_message("Switch to this workstream:", type: :muted)
1228
+ display_message(" cd #{result[:path]}", type: :info)
1229
+ rescue Aidp::Worktree::Error => e
1230
+ display_message("❌ #{e.message}", type: :error)
1231
+ end
1232
+
1233
+ when "rm"
1234
+ # Remove workstream
1235
+ slug = args.shift
1236
+ unless slug
1237
+ display_message("❌ Missing slug", type: :error)
1238
+ display_message("Usage: aidp ws rm <slug> [--delete-branch] [--force]", type: :info)
1239
+ return
1240
+ end
1241
+
1242
+ delete_branch = args.include?("--delete-branch")
1243
+ force = args.include?("--force")
1244
+
1245
+ # Confirm removal unless --force
1246
+ unless force
1247
+ prompt = TTY::Prompt.new
1248
+ confirm = prompt.yes?("Remove workstream '#{slug}'?#{" (will also delete branch)" if delete_branch}")
1249
+ return unless confirm
1250
+ end
1251
+
1252
+ begin
1253
+ Aidp::Worktree.remove(
1254
+ slug: slug,
1255
+ project_dir: Dir.pwd,
1256
+ delete_branch: delete_branch
1257
+ )
1258
+
1259
+ display_message("✓ Removed workstream: #{slug}", type: :success)
1260
+ display_message(" Branch deleted", type: :info) if delete_branch
1261
+ rescue Aidp::Worktree::Error => e
1262
+ display_message("❌ #{e.message}", type: :error)
1263
+ end
1264
+
1265
+ when "status"
1266
+ # Show workstream status
1267
+ slug = args.shift
1268
+ unless slug
1269
+ display_message("❌ Missing slug", type: :error)
1270
+ display_message("Usage: aidp ws status <slug>", type: :info)
1271
+ return
1272
+ end
1273
+
1274
+ begin
1275
+ ws = Aidp::Worktree.info(slug: slug, project_dir: Dir.pwd)
1276
+ unless ws
1277
+ display_message("❌ Workstream not found: #{slug}", type: :error)
1278
+ return
1279
+ end
1280
+
1281
+ state = Aidp::WorkstreamState.read(slug: slug, project_dir: Dir.pwd) || {}
1282
+ iterations = state[:iterations] || 0
1283
+ elapsed = Aidp::WorkstreamState.elapsed_seconds(slug: slug, project_dir: Dir.pwd)
1284
+ task = state[:task]
1285
+ recent_events = Aidp::WorkstreamState.recent_events(slug: slug, project_dir: Dir.pwd, limit: 5)
1286
+ display_message("Workstream: #{slug}", type: :highlight)
1287
+ display_message("=" * 60, type: :muted)
1288
+ display_message("Path: #{ws[:path]}", type: :info)
1289
+ display_message("Branch: #{ws[:branch]}", type: :info)
1290
+ display_message("Created: #{Time.parse(ws[:created_at]).strftime("%Y-%m-%d %H:%M:%S")}", type: :info)
1291
+ display_message("Status: #{ws[:active] ? "Active" : "Inactive"}", type: ws[:active] ? :success : :error)
1292
+ display_message("Iterations: #{iterations}", type: :info)
1293
+ display_message("Elapsed: #{elapsed}s", type: :info)
1294
+ display_message("Task: #{task}", type: :info) if task
1295
+ if recent_events.any?
1296
+ display_message("", type: :info)
1297
+ display_message("Recent Events:", type: :highlight)
1298
+ recent_events.each do |ev|
1299
+ display_message(" #{ev[:timestamp]} #{ev[:type]} #{ev[:data].inspect if ev[:data]}", type: :muted)
1300
+ end
1301
+ end
1302
+
1303
+ # Show git status if active
1304
+ if ws[:active] && Dir.exist?(ws[:path])
1305
+ display_message("", type: :info)
1306
+ display_message("Git Status:", type: :highlight)
1307
+ Dir.chdir(ws[:path]) do
1308
+ system("git", "status", "--short")
1309
+ end
1310
+ end
1311
+ rescue Aidp::Worktree::Error => e
1312
+ display_message("❌ #{e.message}", type: :error)
1313
+ end
1314
+
1315
+ when "pause"
1316
+ # Pause workstream
1317
+ slug = args.shift
1318
+ unless slug
1319
+ display_message("❌ Missing slug", type: :error)
1320
+ display_message("Usage: aidp ws pause <slug>", type: :info)
1321
+ return
1322
+ end
1323
+
1324
+ result = Aidp::WorkstreamState.pause(slug: slug, project_dir: Dir.pwd)
1325
+ if result[:error]
1326
+ display_message("❌ #{result[:error]}", type: :error)
1327
+ else
1328
+ display_message("⏸️ Paused workstream: #{slug}", type: :success)
1329
+ end
1330
+
1331
+ when "resume"
1332
+ # Resume workstream
1333
+ slug = args.shift
1334
+ unless slug
1335
+ display_message("❌ Missing slug", type: :error)
1336
+ display_message("Usage: aidp ws resume <slug>", type: :info)
1337
+ return
1338
+ end
1339
+
1340
+ result = Aidp::WorkstreamState.resume(slug: slug, project_dir: Dir.pwd)
1341
+ if result[:error]
1342
+ display_message("❌ #{result[:error]}", type: :error)
1343
+ else
1344
+ display_message("▶️ Resumed workstream: #{slug}", type: :success)
1345
+ end
1346
+
1347
+ when "complete"
1348
+ # Mark workstream as completed
1349
+ slug = args.shift
1350
+ unless slug
1351
+ display_message("❌ Missing slug", type: :error)
1352
+ display_message("Usage: aidp ws complete <slug>", type: :info)
1353
+ return
1354
+ end
1355
+
1356
+ result = Aidp::WorkstreamState.complete(slug: slug, project_dir: Dir.pwd)
1357
+ if result[:error]
1358
+ display_message("❌ #{result[:error]}", type: :error)
1359
+ else
1360
+ display_message("✅ Completed workstream: #{slug}", type: :success)
1361
+ end
1362
+
1363
+ when "run"
1364
+ # Run one or more workstreams in parallel
1365
+ require_relative "workstream_executor"
1366
+
1367
+ slugs = []
1368
+ max_concurrent = 3
1369
+ mode = :execute
1370
+ selected_steps = nil
1371
+
1372
+ until args.empty?
1373
+ token = args.shift
1374
+ case token
1375
+ when "--max-concurrent"
1376
+ max_concurrent = args.shift.to_i
1377
+ when "--mode"
1378
+ mode = args.shift&.to_sym || :execute
1379
+ when "--steps"
1380
+ selected_steps = args.shift.split(",")
1381
+ else
1382
+ slugs << token
1383
+ end
1384
+ end
1385
+
1386
+ if slugs.empty?
1387
+ display_message("❌ Missing workstream slug(s)", type: :error)
1388
+ display_message("Usage: aidp ws run <slug1> [slug2...] [--max-concurrent N] [--mode analyze|execute] [--steps STEP1,STEP2]", type: :info)
1389
+ display_message("", type: :info)
1390
+ display_message("Examples:", type: :info)
1391
+ display_message(" aidp ws run issue-123 # Run single workstream", type: :info)
1392
+ display_message(" aidp ws run issue-123 issue-456 feature-x # Run multiple in parallel", type: :info)
1393
+ display_message(" aidp ws run issue-* --max-concurrent 5 # Run all matching (expand glob first)", type: :info)
1394
+ return
1395
+ end
1396
+
1397
+ begin
1398
+ executor = Aidp::WorkstreamExecutor.new(project_dir: Dir.pwd, max_concurrent: max_concurrent)
1399
+ options = {mode: mode}
1400
+ options[:selected_steps] = selected_steps if selected_steps
1401
+
1402
+ results = executor.execute_parallel(slugs, options)
1403
+
1404
+ # Show results
1405
+ display_message("", type: :info)
1406
+ success_count = results.count { |r| r.status == "completed" }
1407
+ if success_count == results.size
1408
+ display_message("🎉 All workstreams completed successfully!", type: :success)
1409
+ else
1410
+ display_message("⚠️ Some workstreams failed", type: :warn)
1411
+ end
1412
+ rescue => e
1413
+ display_message("❌ Parallel execution error: #{e.message}", type: :error)
1414
+ end
1415
+
1416
+ when "run-all"
1417
+ # Run all active workstreams in parallel
1418
+ require_relative "workstream_executor"
1419
+
1420
+ max_concurrent = 3
1421
+ mode = :execute
1422
+ selected_steps = nil
1423
+
1424
+ until args.empty?
1425
+ token = args.shift
1426
+ case token
1427
+ when "--max-concurrent"
1428
+ max_concurrent = args.shift.to_i
1429
+ when "--mode"
1430
+ mode = args.shift&.to_sym || :execute
1431
+ when "--steps"
1432
+ selected_steps = args.shift.split(",")
1433
+ end
1434
+ end
1435
+
1436
+ begin
1437
+ executor = Aidp::WorkstreamExecutor.new(project_dir: Dir.pwd, max_concurrent: max_concurrent)
1438
+ options = {mode: mode}
1439
+ options[:selected_steps] = selected_steps if selected_steps
1440
+
1441
+ results = executor.execute_all(options)
1442
+
1443
+ if results.empty?
1444
+ display_message("⚠️ No active workstreams to run", type: :warn)
1445
+ return
1446
+ end
1447
+
1448
+ # Show results
1449
+ display_message("", type: :info)
1450
+ success_count = results.count { |r| r.status == "completed" }
1451
+ if success_count == results.size
1452
+ display_message("🎉 All workstreams completed successfully!", type: :success)
1453
+ else
1454
+ display_message("⚠️ Some workstreams failed", type: :warn)
1455
+ end
1456
+ rescue => e
1457
+ display_message("❌ Parallel execution error: #{e.message}", type: :error)
1458
+ end
1459
+
1460
+ when "dashboard"
1461
+ # Show multi-workstream dashboard
1462
+ workstreams = Aidp::Worktree.list(project_dir: Dir.pwd)
1463
+
1464
+ if workstreams.empty?
1465
+ display_message("No workstreams found.", type: :info)
1466
+ display_message("Create one with: aidp ws new <slug> [task]", type: :muted)
1467
+ return
1468
+ end
1469
+
1470
+ display_message("Workstreams Dashboard", type: :highlight)
1471
+ display_message("=" * 120, type: :muted)
1472
+
1473
+ # Aggregate state from all workstreams
1474
+ table_rows = workstreams.map do |ws|
1475
+ state = Aidp::WorkstreamState.read(slug: ws[:slug], project_dir: Dir.pwd) || {}
1476
+ status = state[:status] || "active"
1477
+ iterations = state[:iterations] || 0
1478
+ elapsed = Aidp::WorkstreamState.elapsed_seconds(slug: ws[:slug], project_dir: Dir.pwd)
1479
+ task = state[:task] && state[:task].to_s[0, 30]
1480
+ recent_events = Aidp::WorkstreamState.recent_events(slug: ws[:slug], project_dir: Dir.pwd, limit: 1)
1481
+ recent_event = recent_events.first
1482
+ event_summary = if recent_event
1483
+ "#{recent_event[:type]} (#{Time.parse(recent_event[:timestamp]).strftime("%H:%M")})"
1484
+ else
1485
+ "—"
1486
+ end
1487
+
1488
+ status_icon = case status
1489
+ when "active" then "▶️"
1490
+ when "paused" then "⏸️"
1491
+ when "completed" then "✅"
1492
+ when "removed" then "❌"
1493
+ else "?"
1494
+ end
1495
+
1496
+ [
1497
+ status_icon,
1498
+ ws[:slug],
1499
+ status,
1500
+ iterations,
1501
+ "#{elapsed}s",
1502
+ task || "—",
1503
+ event_summary
1504
+ ]
1505
+ end
1506
+
1507
+ header = ["", "Slug", "Status", "Iter", "Elapsed", "Task", "Recent Event"]
1508
+ table = TTY::Table.new(header, table_rows)
1509
+ # Render with explicit width for non-TTY environments
1510
+ renderer = if $stdout.tty?
1511
+ table.render(:basic)
1512
+ else
1513
+ table.render(:basic, width: 120)
1514
+ end
1515
+ display_message(renderer, type: :info)
1516
+
1517
+ # Show summary counts
1518
+ display_message("", type: :info)
1519
+ status_counts = workstreams.group_by do |ws|
1520
+ state = Aidp::WorkstreamState.read(slug: ws[:slug], project_dir: Dir.pwd) || {}
1521
+ state[:status] || "active"
1522
+ end
1523
+ summary_parts = status_counts.map { |status, ws_list| "#{status}: #{ws_list.size}" }
1524
+ display_message("Summary: #{summary_parts.join(", ")}", type: :muted)
1525
+
1526
+ when "pause-all"
1527
+ # Pause all active workstreams
1528
+ workstreams = Aidp::Worktree.list(project_dir: Dir.pwd)
1529
+ paused_count = 0
1530
+ workstreams.each do |ws|
1531
+ state = Aidp::WorkstreamState.read(slug: ws[:slug], project_dir: Dir.pwd)
1532
+ next unless state && state[:status] == "active"
1533
+ result = Aidp::WorkstreamState.pause(slug: ws[:slug], project_dir: Dir.pwd)
1534
+ paused_count += 1 unless result[:error]
1535
+ end
1536
+ display_message("⏸️ Paused #{paused_count} workstream(s)", type: :success)
1537
+
1538
+ when "resume-all"
1539
+ # Resume all paused workstreams
1540
+ workstreams = Aidp::Worktree.list(project_dir: Dir.pwd)
1541
+ resumed_count = 0
1542
+ workstreams.each do |ws|
1543
+ state = Aidp::WorkstreamState.read(slug: ws[:slug], project_dir: Dir.pwd)
1544
+ next unless state && state[:status] == "paused"
1545
+ result = Aidp::WorkstreamState.resume(slug: ws[:slug], project_dir: Dir.pwd)
1546
+ resumed_count += 1 unless result[:error]
1547
+ end
1548
+ display_message("▶️ Resumed #{resumed_count} workstream(s)", type: :success)
1549
+
1550
+ when "stop-all"
1551
+ # Complete all active workstreams
1552
+ workstreams = Aidp::Worktree.list(project_dir: Dir.pwd)
1553
+ stopped_count = 0
1554
+ workstreams.each do |ws|
1555
+ state = Aidp::WorkstreamState.read(slug: ws[:slug], project_dir: Dir.pwd)
1556
+ next unless state && state[:status] == "active"
1557
+ result = Aidp::WorkstreamState.complete(slug: ws[:slug], project_dir: Dir.pwd)
1558
+ stopped_count += 1 unless result[:error]
1559
+ end
1560
+ display_message("⏹️ Stopped #{stopped_count} workstream(s)", type: :success)
1561
+
1562
+ else
1563
+ display_message("Usage: aidp ws <command>", type: :info)
1564
+ display_message("", type: :info)
1565
+ display_message("Commands:", type: :info)
1566
+ display_message(" list List all workstreams (default)", type: :info)
1567
+ display_message(" new <slug> [task] Create new workstream", type: :info)
1568
+ display_message(" rm <slug> Remove workstream", type: :info)
1569
+ display_message(" status <slug> Show workstream status", type: :info)
1570
+ display_message(" run <slug...> Run workstream(s) in parallel", type: :info)
1571
+ display_message(" run-all Run all active workstreams in parallel", type: :info)
1572
+ display_message(" dashboard Show multi-workstream dashboard", type: :info)
1573
+ display_message(" pause <slug> Pause workstream execution", type: :info)
1574
+ display_message(" resume <slug> Resume paused workstream", type: :info)
1575
+ display_message(" complete <slug> Mark workstream as completed", type: :info)
1576
+ display_message("", type: :info)
1577
+ display_message("Options:", type: :info)
1578
+ display_message(" --base-branch <branch> Branch to create from (for 'new')", type: :info)
1579
+ display_message(" --delete-branch Also delete git branch (for 'rm')", type: :info)
1580
+ display_message(" --force Skip confirmation (for 'rm')", type: :info)
1581
+ display_message(" --max-concurrent N Max parallel workstreams (for 'run', 'run-all')", type: :info)
1582
+ display_message(" --mode analyze|execute Execution mode (for 'run', 'run-all')", type: :info)
1583
+ display_message(" --steps STEP1,STEP2 Specific steps to run (for 'run', 'run-all')", type: :info)
1584
+ display_message("", type: :info)
1585
+ display_message("Examples:", type: :info)
1586
+ display_message(" aidp ws list # List workstreams", type: :info)
1587
+ display_message(" aidp ws new issue-123 Fix authentication bug # Create workstream", type: :info)
1588
+ display_message(" aidp ws new feature-x --base-branch develop # Create from branch", type: :info)
1589
+ display_message(" aidp ws status issue-123 # Show status", type: :info)
1590
+ display_message(" aidp ws run issue-123 # Run single workstream", type: :info)
1591
+ display_message(" aidp ws run issue-123 feature-x --max-concurrent 5 # Run multiple in parallel", type: :info)
1592
+ display_message(" aidp ws run-all --max-concurrent 3 # Run all active workstreams", type: :info)
1593
+ display_message(" aidp ws dashboard # Monitor all workstreams", type: :info)
1594
+ display_message(" aidp ws rm issue-123 # Remove workstream", type: :info)
1595
+ display_message(" aidp ws rm issue-123 --delete-branch --force # Force remove with branch", type: :info)
1596
+ end
1597
+ end
1598
+
1599
+ def run_work_command(args)
1600
+ require_relative "worktree"
1601
+ require_relative "harness/state_manager"
1602
+
1603
+ # Parse options
1604
+ workstream_slug = nil
1605
+ mode = :execute
1606
+ background = false
1607
+
1608
+ until args.empty?
1609
+ token = args.shift
1610
+ case token
1611
+ when "--workstream"
1612
+ workstream_slug = args.shift
1613
+ when "--mode"
1614
+ mode = args.shift&.to_sym || :execute
1615
+ when "--background"
1616
+ background = true
1617
+ else
1618
+ display_message("⚠️ Unknown work option: #{token}", type: :warn)
1619
+ end
1620
+ end
1621
+
1622
+ unless workstream_slug
1623
+ display_message("❌ Missing required --workstream flag", type: :error)
1624
+ display_message("Usage: aidp work --workstream <slug> [--mode analyze|execute] [--background]", type: :info)
1625
+ return
1626
+ end
1627
+
1628
+ # Verify workstream exists
1629
+ ws = Aidp::Worktree.info(slug: workstream_slug, project_dir: Dir.pwd)
1630
+ unless ws
1631
+ display_message("❌ Workstream not found: #{workstream_slug}", type: :error)
1632
+ return
1633
+ end
1634
+
1635
+ display_message("🚀 Starting #{mode} mode in workstream: #{workstream_slug}", type: :highlight)
1636
+ display_message(" Path: #{ws[:path]}", type: :info)
1637
+ display_message(" Branch: #{ws[:branch]}", type: :info)
1638
+
1639
+ if background
1640
+ require_relative "jobs/background_runner"
1641
+ runner = Aidp::Jobs::BackgroundRunner.new(Dir.pwd)
1642
+
1643
+ display_message("Starting in background...", type: :info)
1644
+ job_id = runner.start(mode, {workstream: workstream_slug})
1645
+
1646
+ display_message("✓ Started background job: #{job_id}", type: :success)
1647
+ display_message("", type: :info)
1648
+ display_message("Monitor progress:", type: :info)
1649
+ display_message(" aidp jobs status #{job_id}", type: :info)
1650
+ display_message(" aidp jobs logs #{job_id} --tail", type: :info)
1651
+ display_message(" aidp ws status #{workstream_slug}", type: :info)
1652
+ else
1653
+ # Run harness inline with workstream context
1654
+ state_manager = Aidp::Harness::StateManager.new(Dir.pwd, mode)
1655
+ state_manager.set_workstream(workstream_slug)
1656
+
1657
+ # Launch harness (will cd into workstream path via enhanced_runner)
1658
+ display_message("Starting interactive harness...", type: :info)
1659
+ display_message("Press Ctrl+C to stop", type: :highlight)
1660
+
1661
+ # Re-use existing CLI.run harness launch logic but skip first-run wizard
1662
+ # Initialize the enhanced TUI
1663
+ require_relative "harness/ui/enhanced_tui"
1664
+ require_relative "harness/ui/enhanced_workflow_selector"
1665
+ require_relative "harness/enhanced_runner"
1666
+
1667
+ tui = Aidp::Harness::UI::EnhancedTUI.new
1668
+ workflow_selector = Aidp::Harness::UI::EnhancedWorkflowSelector.new(tui, project_dir: Dir.pwd)
1669
+
1670
+ # Start TUI display loop
1671
+ tui.start_display_loop
1672
+
1673
+ begin
1674
+ # Get workflow configuration
1675
+ workflow_config = workflow_selector.select_workflow(harness_mode: false, mode: mode)
1676
+ actual_mode = workflow_config[:mode] || mode
1677
+
1678
+ # Pass workflow configuration to harness
1679
+ harness_options = {
1680
+ mode: actual_mode,
1681
+ workflow_type: workflow_config[:workflow_type],
1682
+ selected_steps: workflow_config[:steps],
1683
+ user_input: workflow_config[:user_input]
1684
+ }
1685
+
1686
+ # Create and run the enhanced harness
1687
+ harness_runner = Aidp::Harness::EnhancedRunner.new(Dir.pwd, actual_mode, harness_options)
1688
+ result = harness_runner.run
1689
+ display_harness_result(result)
1690
+ rescue Interrupt
1691
+ display_message("\n\n⏹️ Interrupted by user", type: :warning)
1692
+ ensure
1693
+ tui.stop_display_loop
1694
+ end
1695
+ end
1696
+ end
1697
+
1101
1698
  def display_config_usage
1102
1699
  display_message("Usage: aidp config --interactive [--dry-run]", type: :info)
1103
1700
  end
1701
+
1702
+ def run_skill_command(args)
1703
+ require_relative "skills"
1704
+ require "tty-table"
1705
+
1706
+ subcommand = args.shift
1707
+
1708
+ case subcommand
1709
+ when "list", nil
1710
+ # List all available skills
1711
+ begin
1712
+ registry = Aidp::Skills::Registry.new(project_dir: Dir.pwd)
1713
+ registry.load_skills
1714
+
1715
+ skills = registry.all
1716
+
1717
+ if skills.empty?
1718
+ display_message("No skills found.", type: :info)
1719
+ display_message("Create one in skills/ or .aidp/skills/", type: :muted)
1720
+ return
1721
+ end
1722
+
1723
+ by_source = registry.by_source
1724
+
1725
+ if by_source[:builtin].any?
1726
+ display_message("Built-in Skills", type: :highlight)
1727
+ display_message("=" * 80, type: :muted)
1728
+ table_rows = by_source[:builtin].map do |skill_id|
1729
+ skill = registry.find(skill_id)
1730
+ [skill_id, skill.version, skill.description[0, 60]]
1731
+ end
1732
+ header = ["ID", "Version", "Description"]
1733
+ table = TTY::Table.new(header, table_rows)
1734
+ display_message(table.render(:basic), type: :info)
1735
+ display_message("", type: :info)
1736
+ end
1737
+
1738
+ if by_source[:custom].any?
1739
+ display_message("Custom Skills", type: :highlight)
1740
+ display_message("=" * 80, type: :muted)
1741
+ table_rows = by_source[:custom].map do |skill_id|
1742
+ skill = registry.find(skill_id)
1743
+ [skill_id, skill.version, skill.description[0, 60]]
1744
+ end
1745
+ header = ["ID", "Version", "Description"]
1746
+ table = TTY::Table.new(header, table_rows)
1747
+ display_message(table.render(:basic), type: :info)
1748
+ display_message("", type: :info)
1749
+ end
1750
+
1751
+ display_message("Use 'aidp skill show <id>' for details", type: :muted)
1752
+ rescue => e
1753
+ display_message("Failed to list skills: #{e.message}", type: :error)
1754
+ end
1755
+
1756
+ when "show"
1757
+ # Show detailed skill information
1758
+ skill_id = args.shift
1759
+
1760
+ unless skill_id
1761
+ display_message("Usage: aidp skill show <skill-id>", type: :info)
1762
+ return
1763
+ end
1764
+
1765
+ begin
1766
+ registry = Aidp::Skills::Registry.new(project_dir: Dir.pwd)
1767
+ registry.load_skills
1768
+
1769
+ skill = registry.find(skill_id)
1770
+
1771
+ unless skill
1772
+ display_message("Skill not found: #{skill_id}", type: :error)
1773
+ display_message("Use 'aidp skill list' to see available skills", type: :muted)
1774
+ return
1775
+ end
1776
+
1777
+ details = skill.details
1778
+
1779
+ display_message("Skill: #{details[:name]} (#{details[:id]})", type: :highlight)
1780
+ display_message("=" * 80, type: :muted)
1781
+ display_message("Version: #{details[:version]}", type: :info)
1782
+ display_message("Source: #{details[:source]}", type: :muted)
1783
+ display_message("", type: :info)
1784
+ display_message("Description:", type: :info)
1785
+ display_message(" #{details[:description]}", type: :info)
1786
+ display_message("", type: :info)
1787
+
1788
+ if details[:expertise].any?
1789
+ display_message("Expertise:", type: :info)
1790
+ details[:expertise].each { |e| display_message(" • #{e}", type: :info) }
1791
+ display_message("", type: :info)
1792
+ end
1793
+
1794
+ if details[:keywords].any?
1795
+ display_message("Keywords: #{details[:keywords].join(", ")}", type: :info)
1796
+ display_message("", type: :info)
1797
+ end
1798
+
1799
+ if details[:when_to_use].any?
1800
+ display_message("When to Use:", type: :info)
1801
+ details[:when_to_use].each { |w| display_message(" • #{w}", type: :info) }
1802
+ display_message("", type: :info)
1803
+ end
1804
+
1805
+ if details[:when_not_to_use].any?
1806
+ display_message("When NOT to Use:", type: :info)
1807
+ details[:when_not_to_use].each { |w| display_message(" • #{w}", type: :info) }
1808
+ display_message("", type: :info)
1809
+ end
1810
+
1811
+ if details[:compatible_providers].any?
1812
+ display_message("Compatible Providers: #{details[:compatible_providers].join(", ")}", type: :info)
1813
+ else
1814
+ display_message("Compatible Providers: all", type: :info)
1815
+ end
1816
+ rescue => e
1817
+ display_message("Failed to show skill: #{e.message}", type: :error)
1818
+ end
1819
+
1820
+ when "search"
1821
+ # Search skills by query
1822
+ query = args.join(" ")
1823
+
1824
+ unless query && !query.empty?
1825
+ display_message("Usage: aidp skill search <query>", type: :info)
1826
+ return
1827
+ end
1828
+
1829
+ begin
1830
+ registry = Aidp::Skills::Registry.new(project_dir: Dir.pwd)
1831
+ registry.load_skills
1832
+
1833
+ matching_skills = registry.search(query)
1834
+
1835
+ if matching_skills.empty?
1836
+ display_message("No skills found matching '#{query}'", type: :info)
1837
+ return
1838
+ end
1839
+
1840
+ display_message("Skills matching '#{query}':", type: :highlight)
1841
+ display_message("=" * 80, type: :muted)
1842
+ matching_skills.each do |skill|
1843
+ display_message(" • #{skill.id} - #{skill.description}", type: :info)
1844
+ end
1845
+ rescue => e
1846
+ display_message("Failed to search skills: #{e.message}", type: :error)
1847
+ end
1848
+
1849
+ when "validate"
1850
+ # Validate skill file format
1851
+ skill_path = args.shift
1852
+
1853
+ if skill_path
1854
+ # Validate specific file
1855
+ unless File.exist?(skill_path)
1856
+ display_message("File not found: #{skill_path}", type: :error)
1857
+ return
1858
+ end
1859
+
1860
+ begin
1861
+ Aidp::Skills::Loader.load_from_file(skill_path)
1862
+ display_message("✓ Valid skill file: #{skill_path}", type: :success)
1863
+ rescue Aidp::Errors::ValidationError => e
1864
+ display_message("✗ Invalid skill file: #{skill_path}", type: :error)
1865
+ display_message(" #{e.message}", type: :error)
1866
+ end
1867
+ else
1868
+ # Validate all skills in registry
1869
+ begin
1870
+ registry = Aidp::Skills::Registry.new(project_dir: Dir.pwd)
1871
+ registry.load_skills
1872
+
1873
+ skills = registry.all
1874
+
1875
+ if skills.empty?
1876
+ display_message("No skills found to validate", type: :info)
1877
+ return
1878
+ end
1879
+
1880
+ display_message("Validating #{skills.size} skill(s)...", type: :info)
1881
+ display_message("", type: :info)
1882
+
1883
+ valid_count = 0
1884
+ skills.each do |skill|
1885
+ display_message("✓ #{skill.id} (v#{skill.version})", type: :success)
1886
+ valid_count += 1
1887
+ end
1888
+
1889
+ display_message("", type: :info)
1890
+ display_message("#{valid_count}/#{skills.size} skills are valid", type: :success)
1891
+ rescue => e
1892
+ display_message("Validation failed: #{e.message}", type: :error)
1893
+ end
1894
+ end
1895
+
1896
+ else
1897
+ display_message("Usage: aidp skill <command>", type: :info)
1898
+ display_message("", type: :info)
1899
+ display_message("Commands:", type: :info)
1900
+ display_message(" list List all available skills (default)", type: :info)
1901
+ display_message(" show <id> Show detailed skill information", type: :info)
1902
+ display_message(" search <query> Search skills by keyword", type: :info)
1903
+ display_message(" validate [path] Validate skill file format", type: :info)
1904
+ display_message("", type: :info)
1905
+ display_message("Examples:", type: :info)
1906
+ display_message(" aidp skill list # List all skills", type: :info)
1907
+ display_message(" aidp skill show repository_analyst # Show skill details", type: :info)
1908
+ display_message(" aidp skill search git # Search for git-related skills", type: :info)
1909
+ display_message(" aidp skill validate skills/my_skill/SKILL.md # Validate specific skill", type: :info)
1910
+ display_message(" aidp skill validate # Validate all skills", type: :info)
1911
+ end
1912
+ end
1104
1913
  end # class << self
1105
1914
  end
1106
1915
  end