aidp 0.15.2 → 0.17.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 (67) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +47 -0
  3. data/lib/aidp/analyze/error_handler.rb +46 -28
  4. data/lib/aidp/analyze/progress.rb +1 -1
  5. data/lib/aidp/analyze/runner.rb +27 -5
  6. data/lib/aidp/analyze/steps.rb +4 -0
  7. data/lib/aidp/cli/jobs_command.rb +2 -1
  8. data/lib/aidp/cli.rb +1086 -4
  9. data/lib/aidp/concurrency/backoff.rb +148 -0
  10. data/lib/aidp/concurrency/exec.rb +192 -0
  11. data/lib/aidp/concurrency/wait.rb +148 -0
  12. data/lib/aidp/concurrency.rb +71 -0
  13. data/lib/aidp/config.rb +21 -1
  14. data/lib/aidp/daemon/runner.rb +9 -8
  15. data/lib/aidp/debug_mixin.rb +1 -0
  16. data/lib/aidp/errors.rb +12 -0
  17. data/lib/aidp/execute/async_work_loop_runner.rb +2 -1
  18. data/lib/aidp/execute/checkpoint.rb +1 -1
  19. data/lib/aidp/execute/future_work_backlog.rb +1 -1
  20. data/lib/aidp/execute/interactive_repl.rb +102 -11
  21. data/lib/aidp/execute/progress.rb +1 -1
  22. data/lib/aidp/execute/repl_macros.rb +845 -2
  23. data/lib/aidp/execute/runner.rb +27 -5
  24. data/lib/aidp/execute/steps.rb +2 -0
  25. data/lib/aidp/harness/config_loader.rb +24 -2
  26. data/lib/aidp/harness/config_validator.rb +1 -1
  27. data/lib/aidp/harness/enhanced_runner.rb +16 -2
  28. data/lib/aidp/harness/error_handler.rb +1 -1
  29. data/lib/aidp/harness/provider_info.rb +19 -15
  30. data/lib/aidp/harness/provider_manager.rb +47 -41
  31. data/lib/aidp/harness/runner.rb +3 -11
  32. data/lib/aidp/harness/state/persistence.rb +1 -6
  33. data/lib/aidp/harness/state_manager.rb +115 -7
  34. data/lib/aidp/harness/status_display.rb +11 -18
  35. data/lib/aidp/harness/ui/navigation/submenu.rb +1 -0
  36. data/lib/aidp/harness/ui/workflow_controller.rb +1 -1
  37. data/lib/aidp/harness/user_interface.rb +12 -15
  38. data/lib/aidp/jobs/background_runner.rb +16 -6
  39. data/lib/aidp/providers/codex.rb +0 -1
  40. data/lib/aidp/providers/cursor.rb +0 -1
  41. data/lib/aidp/providers/github_copilot.rb +0 -1
  42. data/lib/aidp/providers/opencode.rb +0 -1
  43. data/lib/aidp/skills/composer.rb +178 -0
  44. data/lib/aidp/skills/loader.rb +205 -0
  45. data/lib/aidp/skills/registry.rb +222 -0
  46. data/lib/aidp/skills/router.rb +178 -0
  47. data/lib/aidp/skills/skill.rb +174 -0
  48. data/lib/aidp/skills/wizard/builder.rb +141 -0
  49. data/lib/aidp/skills/wizard/controller.rb +145 -0
  50. data/lib/aidp/skills/wizard/differ.rb +232 -0
  51. data/lib/aidp/skills/wizard/prompter.rb +317 -0
  52. data/lib/aidp/skills/wizard/template_library.rb +164 -0
  53. data/lib/aidp/skills/wizard/writer.rb +105 -0
  54. data/lib/aidp/skills.rb +30 -0
  55. data/lib/aidp/version.rb +1 -1
  56. data/lib/aidp/watch/build_processor.rb +93 -28
  57. data/lib/aidp/watch/runner.rb +3 -2
  58. data/lib/aidp/workstream_executor.rb +244 -0
  59. data/lib/aidp/workstream_state.rb +212 -0
  60. data/lib/aidp/worktree.rb +208 -0
  61. data/lib/aidp.rb +6 -0
  62. data/templates/skills/README.md +334 -0
  63. data/templates/skills/architecture_analyst/SKILL.md +173 -0
  64. data/templates/skills/product_strategist/SKILL.md +141 -0
  65. data/templates/skills/repository_analyst/SKILL.md +117 -0
  66. data/templates/skills/test_analyzer/SKILL.md +213 -0
  67. metadata +29 -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
@@ -240,7 +241,7 @@ module Aidp
240
241
 
241
242
  if File.exist?(config_path)
242
243
  require "yaml"
243
- full_config = YAML.load_file(config_path)
244
+ full_config = YAML.safe_load_file(config_path, permitted_classes: [Date, Time, Symbol], aliases: true)
244
245
  logging_config = full_config["logging"] || full_config[:logging] || {}
245
246
  end
246
247
 
@@ -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,1054 @@ 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[:template].any?
1726
+ display_message("Template Skills", type: :highlight)
1727
+ display_message("=" * 80, type: :muted)
1728
+ table_rows = by_source[:template].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[:project].any?
1739
+ display_message("Project Skills", type: :highlight)
1740
+ display_message("=" * 80, type: :muted)
1741
+ table_rows = by_source[:project].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 "preview"
1850
+ # Preview full skill content
1851
+ skill_id = args.shift
1852
+
1853
+ unless skill_id
1854
+ display_message("Usage: aidp skill preview <skill-id>", type: :info)
1855
+ return
1856
+ end
1857
+
1858
+ begin
1859
+ registry = Aidp::Skills::Registry.new(project_dir: Dir.pwd)
1860
+ registry.load_skills
1861
+
1862
+ skill = registry.find(skill_id)
1863
+
1864
+ unless skill
1865
+ display_message("Skill not found: #{skill_id}", type: :error)
1866
+ display_message("Use 'aidp skill list' to see available skills", type: :muted)
1867
+ return
1868
+ end
1869
+
1870
+ require_relative "skills/wizard/builder"
1871
+ require_relative "skills/wizard/template_library"
1872
+
1873
+ builder = Aidp::Skills::Wizard::Builder.new
1874
+ full_content = builder.to_skill_md(skill)
1875
+
1876
+ # Check if this is a project skill with a matching template
1877
+ source_info = registry.by_source[skill_id]
1878
+ inheritance_info = ""
1879
+ if source_info == :project
1880
+ template_library = Aidp::Skills::Wizard::TemplateLibrary.new(project_dir: Dir.pwd)
1881
+ template_skill = template_library.templates.find { |s| s.id == skill.id }
1882
+ if template_skill
1883
+ inheritance_info = " (inherits from template)"
1884
+ end
1885
+ elsif source_info == :template
1886
+ inheritance_info = " (template)"
1887
+ end
1888
+
1889
+ display_message("\n" + "=" * 60, type: :info)
1890
+ display_message("Skill: #{skill.name} (#{skill.id}) v#{skill.version}#{inheritance_info}", type: :highlight)
1891
+ display_message("=" * 60 + "\n", type: :info)
1892
+ display_message(full_content, type: :info)
1893
+ display_message("\n" + "=" * 60, type: :info)
1894
+ rescue => e
1895
+ display_message("Failed to preview skill: #{e.message}", type: :error)
1896
+ end
1897
+
1898
+ when "diff"
1899
+ # Show diff between project skill and template
1900
+ skill_id = args.shift
1901
+
1902
+ unless skill_id
1903
+ display_message("Usage: aidp skill diff <skill-id>", type: :info)
1904
+ return
1905
+ end
1906
+
1907
+ begin
1908
+ require_relative "skills/wizard/template_library"
1909
+ require_relative "skills/wizard/differ"
1910
+
1911
+ registry = Aidp::Skills::Registry.new(project_dir: Dir.pwd)
1912
+ registry.load_skills
1913
+
1914
+ project_skill = registry.find(skill_id)
1915
+
1916
+ unless project_skill
1917
+ display_message("Skill not found: #{skill_id}", type: :error)
1918
+ return
1919
+ end
1920
+
1921
+ # Check if it's a project skill
1922
+ unless registry.by_source[:project].include?(skill_id)
1923
+ display_message("Skill '#{skill_id}' is a template skill, not a project skill", type: :info)
1924
+ display_message("Only project skills can be diffed against templates", type: :muted)
1925
+ return
1926
+ end
1927
+
1928
+ # Find the template
1929
+ template_library = Aidp::Skills::Wizard::TemplateLibrary.new(project_dir: Dir.pwd)
1930
+ template_skill = template_library.find(skill_id)
1931
+
1932
+ unless template_skill
1933
+ display_message("No template found for skill '#{skill_id}'", type: :info)
1934
+ display_message("This is a custom skill without a template base", type: :muted)
1935
+ return
1936
+ end
1937
+
1938
+ # Show diff
1939
+ differ = Aidp::Skills::Wizard::Differ.new
1940
+ diff_result = differ.diff(template_skill, project_skill)
1941
+ differ.display(diff_result)
1942
+ rescue => e
1943
+ display_message("Failed to diff skill: #{e.message}", type: :error)
1944
+ end
1945
+
1946
+ when "edit"
1947
+ # Edit an existing skill
1948
+ skill_id = args.shift
1949
+
1950
+ unless skill_id
1951
+ display_message("Usage: aidp skill edit <skill-id>", type: :info)
1952
+ return
1953
+ end
1954
+
1955
+ begin
1956
+ require_relative "skills/wizard/controller"
1957
+
1958
+ registry = Aidp::Skills::Registry.new(project_dir: Dir.pwd)
1959
+ registry.load_skills
1960
+
1961
+ skill = registry.find(skill_id)
1962
+
1963
+ unless skill
1964
+ display_message("Skill not found: #{skill_id}", type: :error)
1965
+ display_message("Use 'aidp skill list' to see available skills", type: :muted)
1966
+ return
1967
+ end
1968
+
1969
+ # Check if it's editable (must be project skill or willing to copy template)
1970
+ if registry.by_source[:template].include?(skill_id)
1971
+ display_message("'#{skill_id}' is a template skill", type: :info)
1972
+ display_message("Editing will create a project override in .aidp/skills/", type: :muted)
1973
+ end
1974
+
1975
+ # Parse options
1976
+ options = {}
1977
+ while args.first&.start_with?("--")
1978
+ opt = args.shift
1979
+ case opt
1980
+ when "--dry-run"
1981
+ options[:dry_run] = true
1982
+ when "--open-editor"
1983
+ options[:open_editor] = true
1984
+ else
1985
+ display_message("Unknown option: #{opt}", type: :error)
1986
+ return
1987
+ end
1988
+ end
1989
+
1990
+ # Pre-fill wizard with existing skill data
1991
+ options[:id] = skill.id
1992
+ options[:name] = skill.name
1993
+ options[:edit_mode] = true
1994
+ options[:existing_skill] = skill
1995
+
1996
+ # Run wizard in edit mode
1997
+ wizard = Aidp::Skills::Wizard::Controller.new(
1998
+ project_dir: Dir.pwd,
1999
+ options: options
2000
+ )
2001
+ wizard.run
2002
+ rescue => e
2003
+ display_message("Failed to edit skill: #{e.message}", type: :error)
2004
+ end
2005
+
2006
+ when "new"
2007
+ # Create a new skill using the wizard
2008
+ begin
2009
+ require_relative "skills/wizard/controller"
2010
+
2011
+ # Parse options
2012
+ options = {}
2013
+ while args.first&.start_with?("--")
2014
+ opt = args.shift
2015
+ case opt
2016
+ when "--minimal"
2017
+ options[:minimal] = true
2018
+ when "--dry-run"
2019
+ options[:dry_run] = true
2020
+ when "--yes", "-y"
2021
+ options[:yes] = true
2022
+ when "--id"
2023
+ options[:id] = args.shift
2024
+ when "--name"
2025
+ options[:name] = args.shift
2026
+ when "--from-template"
2027
+ options[:from_template] = args.shift
2028
+ when "--clone"
2029
+ options[:clone] = args.shift
2030
+ else
2031
+ display_message("Unknown option: #{opt}", type: :error)
2032
+ return
2033
+ end
2034
+ end
2035
+
2036
+ # Run wizard
2037
+ wizard = Aidp::Skills::Wizard::Controller.new(
2038
+ project_dir: Dir.pwd,
2039
+ options: options
2040
+ )
2041
+ wizard.run
2042
+ rescue => e
2043
+ display_message("Failed to create skill: #{e.message}", type: :error)
2044
+ Aidp.log_error("cli", "Skill wizard failed", error: e.message, backtrace: e.backtrace.first(5))
2045
+ end
2046
+
2047
+ when "validate"
2048
+ # Validate skill file format
2049
+ skill_path = args.shift
2050
+
2051
+ if skill_path
2052
+ # Validate specific file
2053
+ unless File.exist?(skill_path)
2054
+ display_message("File not found: #{skill_path}", type: :error)
2055
+ return
2056
+ end
2057
+
2058
+ begin
2059
+ Aidp::Skills::Loader.load_from_file(skill_path)
2060
+ display_message("✓ Valid skill file: #{skill_path}", type: :success)
2061
+ rescue Aidp::Errors::ValidationError => e
2062
+ display_message("✗ Invalid skill file: #{skill_path}", type: :error)
2063
+ display_message(" #{e.message}", type: :error)
2064
+ end
2065
+ else
2066
+ # Validate all skills in registry
2067
+ begin
2068
+ registry = Aidp::Skills::Registry.new(project_dir: Dir.pwd)
2069
+ registry.load_skills
2070
+
2071
+ skills = registry.all
2072
+
2073
+ if skills.empty?
2074
+ display_message("No skills found to validate", type: :info)
2075
+ return
2076
+ end
2077
+
2078
+ display_message("Validating #{skills.size} skill(s)...", type: :info)
2079
+ display_message("", type: :info)
2080
+
2081
+ valid_count = 0
2082
+ skills.each do |skill|
2083
+ display_message("✓ #{skill.id} (v#{skill.version})", type: :success)
2084
+ valid_count += 1
2085
+ end
2086
+
2087
+ display_message("", type: :info)
2088
+ display_message("#{valid_count}/#{skills.size} skills are valid", type: :success)
2089
+ rescue => e
2090
+ display_message("Validation failed: #{e.message}", type: :error)
2091
+ end
2092
+ end
2093
+
2094
+ when "delete"
2095
+ # Delete a project skill
2096
+ skill_id = args.shift
2097
+
2098
+ unless skill_id
2099
+ display_message("Usage: aidp skill delete <skill-id>", type: :info)
2100
+ return
2101
+ end
2102
+
2103
+ begin
2104
+ registry = Aidp::Skills::Registry.new(project_dir: Dir.pwd)
2105
+ registry.load_skills
2106
+
2107
+ skill = registry.find(skill_id)
2108
+
2109
+ unless skill
2110
+ display_message("Skill not found: #{skill_id}", type: :error)
2111
+ return
2112
+ end
2113
+
2114
+ # Check if it's a project skill
2115
+ source = registry.by_source[skill_id]
2116
+ unless source == :project
2117
+ display_message("Cannot delete template skill '#{skill_id}'", type: :error)
2118
+ display_message("Only project skills in .aidp/skills/ can be deleted", type: :muted)
2119
+ return
2120
+ end
2121
+
2122
+ # Get skill directory
2123
+ skill_dir = File.dirname(skill.source_path)
2124
+
2125
+ # Confirm deletion
2126
+ require "tty-prompt"
2127
+ prompt = TTY::Prompt.new
2128
+ confirmed = prompt.yes?("Delete skill '#{skill.name}' (#{skill_id})? This cannot be undone.")
2129
+
2130
+ unless confirmed
2131
+ display_message("Deletion cancelled", type: :info)
2132
+ return
2133
+ end
2134
+
2135
+ # Delete the skill directory
2136
+ require "fileutils"
2137
+ FileUtils.rm_rf(skill_dir)
2138
+
2139
+ display_message("✓ Deleted skill: #{skill.name} (#{skill_id})", type: :success)
2140
+ rescue => e
2141
+ display_message("Failed to delete skill: #{e.message}", type: :error)
2142
+ Aidp.log_error("cli", "Skill deletion failed", error: e.message, backtrace: e.backtrace.first(5))
2143
+ end
2144
+
2145
+ else
2146
+ display_message("Usage: aidp skill <command>", type: :info)
2147
+ display_message("", type: :info)
2148
+ display_message("Commands:", type: :info)
2149
+ display_message(" list List all available skills (default)", type: :info)
2150
+ display_message(" show <id> Show detailed skill information", type: :info)
2151
+ display_message(" preview <id> Preview full SKILL.md content", type: :info)
2152
+ display_message(" diff <id> Show diff between project skill and template", type: :info)
2153
+ display_message(" search <query> Search skills by keyword", type: :info)
2154
+ display_message(" new [options] Create a new skill using the wizard", type: :info)
2155
+ display_message(" edit <id> [options] Edit an existing skill", type: :info)
2156
+ display_message(" delete <id> Delete a project skill", type: :info)
2157
+ display_message(" validate [path] Validate skill file format", type: :info)
2158
+ display_message("", type: :info)
2159
+ display_message("New Skill Options:", type: :info)
2160
+ display_message(" --minimal Skip optional sections", type: :info)
2161
+ display_message(" --dry-run Preview without saving", type: :info)
2162
+ display_message(" --yes, -y Skip confirmation prompts", type: :info)
2163
+ display_message(" --id <skill_id> Pre-set skill ID", type: :info)
2164
+ display_message(" --name <name> Pre-set skill name", type: :info)
2165
+ display_message("", type: :info)
2166
+ display_message("Edit Skill Options:", type: :info)
2167
+ display_message(" --dry-run Preview changes without saving", type: :info)
2168
+ display_message(" --open-editor Open content in $EDITOR", type: :info)
2169
+ display_message("", type: :info)
2170
+ display_message("Examples:", type: :info)
2171
+ display_message(" aidp skill list # List all skills", type: :info)
2172
+ display_message(" aidp skill show repository_analyst # Show skill details", type: :info)
2173
+ display_message(" aidp skill preview repository_analyst # Preview full content", type: :info)
2174
+ display_message(" aidp skill diff my_skill # Show diff with template", type: :info)
2175
+ display_message(" aidp skill search git # Search for git-related skills", type: :info)
2176
+ display_message(" aidp skill new # Create new skill (interactive)", type: :info)
2177
+ display_message(" aidp skill new --minimal --id my_skill # Create with minimal prompts", type: :info)
2178
+ display_message(" aidp skill new --from-template repo_analyst # Inherit from template", type: :info)
2179
+ display_message(" aidp skill new --clone my_existing_skill # Clone existing skill", type: :info)
2180
+ display_message(" aidp skill edit repository_analyst # Edit existing skill", type: :info)
2181
+ display_message(" aidp skill delete my_custom_skill # Delete a project skill", type: :info)
2182
+ display_message(" aidp skill validate skills/my_skill/SKILL.md # Validate specific skill", type: :info)
2183
+ display_message(" aidp skill validate # Validate all skills", type: :info)
2184
+ end
2185
+ end
1104
2186
  end # class << self
1105
2187
  end
1106
2188
  end