aidp 0.16.0 → 0.17.1

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 (54) hide show
  1. checksums.yaml +4 -4
  2. data/lib/aidp/analyze/error_handler.rb +32 -13
  3. data/lib/aidp/analyze/kb_inspector.rb +2 -3
  4. data/lib/aidp/analyze/progress.rb +6 -11
  5. data/lib/aidp/cli/mcp_dashboard.rb +1 -1
  6. data/lib/aidp/cli.rb +300 -33
  7. data/lib/aidp/config.rb +1 -1
  8. data/lib/aidp/execute/async_work_loop_runner.rb +2 -1
  9. data/lib/aidp/execute/checkpoint.rb +1 -1
  10. data/lib/aidp/execute/future_work_backlog.rb +1 -1
  11. data/lib/aidp/execute/progress.rb +6 -9
  12. data/lib/aidp/execute/repl_macros.rb +79 -10
  13. data/lib/aidp/harness/config_loader.rb +2 -2
  14. data/lib/aidp/harness/config_validator.rb +1 -1
  15. data/lib/aidp/harness/enhanced_runner.rb +16 -7
  16. data/lib/aidp/harness/error_handler.rb +12 -5
  17. data/lib/aidp/harness/provider_manager.rb +4 -19
  18. data/lib/aidp/harness/runner.rb +2 -2
  19. data/lib/aidp/harness/state/persistence.rb +9 -10
  20. data/lib/aidp/harness/state/workflow_state.rb +3 -2
  21. data/lib/aidp/harness/state_manager.rb +33 -97
  22. data/lib/aidp/harness/status_display.rb +22 -12
  23. data/lib/aidp/harness/ui/enhanced_tui.rb +3 -4
  24. data/lib/aidp/harness/user_interface.rb +11 -6
  25. data/lib/aidp/jobs/background_runner.rb +8 -2
  26. data/lib/aidp/logger.rb +1 -1
  27. data/lib/aidp/message_display.rb +9 -2
  28. data/lib/aidp/providers/anthropic.rb +1 -1
  29. data/lib/aidp/providers/base.rb +4 -4
  30. data/lib/aidp/providers/codex.rb +1 -1
  31. data/lib/aidp/providers/cursor.rb +1 -1
  32. data/lib/aidp/providers/gemini.rb +1 -1
  33. data/lib/aidp/providers/github_copilot.rb +1 -1
  34. data/lib/aidp/providers/macos_ui.rb +1 -1
  35. data/lib/aidp/providers/opencode.rb +1 -1
  36. data/lib/aidp/skills/registry.rb +31 -29
  37. data/lib/aidp/skills/router.rb +178 -0
  38. data/lib/aidp/skills/wizard/builder.rb +141 -0
  39. data/lib/aidp/skills/wizard/controller.rb +145 -0
  40. data/lib/aidp/skills/wizard/differ.rb +232 -0
  41. data/lib/aidp/skills/wizard/prompter.rb +317 -0
  42. data/lib/aidp/skills/wizard/template_library.rb +164 -0
  43. data/lib/aidp/skills/wizard/writer.rb +105 -0
  44. data/lib/aidp/version.rb +1 -1
  45. data/lib/aidp/watch/plan_generator.rb +1 -1
  46. data/lib/aidp/watch/repository_client.rb +13 -9
  47. data/lib/aidp/workflows/guided_agent.rb +2 -312
  48. data/lib/aidp/workstream_executor.rb +8 -2
  49. data/templates/skills/README.md +334 -0
  50. data/templates/skills/architecture_analyst/SKILL.md +173 -0
  51. data/templates/skills/product_strategist/SKILL.md +141 -0
  52. data/templates/skills/repository_analyst/SKILL.md +117 -0
  53. data/templates/skills/test_analyzer/SKILL.md +213 -0
  54. metadata +13 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9bbab73392b0bc0fdcd74bb0f0e0bbf09716b2d2451b04aaa64c4ca4ccc7638e
4
- data.tar.gz: af0d8b6d8937a1781b3b379ab6328ff17c5f0c96ea00c80f5886ce15b2265e2f
3
+ metadata.gz: 949670fdea4721406643f4fd8da096e943740effbd836407311b812350919b14
4
+ data.tar.gz: 8034a3c798571e4626b273c4a9abe9a5da38cef5dd4f25de8794a0ef63bf6022
5
5
  SHA512:
6
- metadata.gz: ee40afa8126a8f8d6f92a7df6fbc634c6eb054499bb7dfdec53946f05ecf8faf8c1bcae1eb02274a7441bdd7685751363e20207587df818c8bb8fe300bfbc50f
7
- data.tar.gz: 010a5afdd1a06905f09a423efe3bfde2ef84aca498f939667871191b1c9174168c31807959cbc3d50e7f7ab2cc552301032c1d232f6e70331ee8918debb7aba5
6
+ metadata.gz: cdabc6ba04d06a89dbc4ad19b27a64862e960daa350029db4fada5a4f6d32d3fba27cb32dda291871a79ad8f319d27e7dd1348ddcb7fdb8b0f0a1390b9493fc1
7
+ data.tar.gz: 39c384f4c01bf00bef550ac9190d27ddd268437be92fc4cdfcdeac3dff09c79c055b2e5b5582e553af6fa838b3f904cce354e9db62ee3638f658eec05673e4f5
@@ -3,6 +3,18 @@
3
3
  require "logger"
4
4
  require_relative "../concurrency"
5
5
 
6
+ begin
7
+ require "net/http"
8
+ rescue LoadError
9
+ # Net::HTTP might not be available in all environments
10
+ end
11
+
12
+ begin
13
+ require "sqlite3"
14
+ rescue LoadError
15
+ # SQLite3 might not be available in all environments
16
+ end
17
+
6
18
  module Aidp
7
19
  module Analyze
8
20
  # Comprehensive error handling system for analyze mode
@@ -24,7 +36,7 @@ module Aidp
24
36
  context: context,
25
37
  step: step,
26
38
  retry_count: retry_count,
27
- timestamp: Time.current
39
+ timestamp: Time.now
28
40
  }
29
41
 
30
42
  log_error(error_info)
@@ -59,7 +71,7 @@ module Aidp
59
71
  {
60
72
  status: "skipped",
61
73
  reason: error.message,
62
- timestamp: Time.current
74
+ timestamp: Time.now
63
75
  }
64
76
  end
65
77
 
@@ -88,8 +100,8 @@ module Aidp
88
100
 
89
101
  def setup_logger(log_file, verbose)
90
102
  output_stream = log_file || @output || $stdout
91
- logger = Logger.new(output_stream)
92
- logger.level = verbose ? Logger::DEBUG : Logger::INFO
103
+ logger = ::Logger.new(output_stream)
104
+ logger.level = verbose ? ::Logger::DEBUG : ::Logger::INFO
93
105
  logger.formatter = proc do |severity, datetime, progname, msg|
94
106
  "#{datetime.strftime("%Y-%m-%d %H:%M:%S")} [#{severity}] #{msg}\n"
95
107
  end
@@ -97,19 +109,28 @@ module Aidp
97
109
  end
98
110
 
99
111
  def setup_recovery_strategies
100
- {
101
- Net::TimeoutError => :retry_with_backoff,
102
- Net::HTTPError => :retry_with_backoff,
103
- SocketError => :retry_with_backoff,
112
+ strategies = {
104
113
  Errno::ENOENT => :skip_step_with_warning,
105
114
  Errno::EACCES => :skip_step_with_warning,
106
115
  Errno::ENOSPC => :critical_error,
107
- SQLite3::BusyException => :retry_with_backoff,
108
- SQLite3::CorruptException => :critical_error,
109
116
  AnalysisTimeoutError => :chunk_and_retry,
110
117
  AnalysisDataError => :continue_with_partial_data,
111
118
  AnalysisToolError => :log_and_continue
112
119
  }
120
+
121
+ # Add network error classes if available
122
+ strategies[Net::TimeoutError] = :retry_with_backoff if defined?(Net::TimeoutError)
123
+
124
+ strategies[Net::HTTPError] = :retry_with_backoff if defined?(Net::HTTPError)
125
+
126
+ strategies[SocketError] = :retry_with_backoff if defined?(SocketError)
127
+
128
+ # Add SQLite error classes if available
129
+ strategies[SQLite3::BusyException] = :retry_with_backoff if defined?(SQLite3::BusyException)
130
+
131
+ strategies[SQLite3::CorruptException] = :critical_error if defined?(SQLite3::CorruptException)
132
+
133
+ strategies
113
134
  end
114
135
 
115
136
  def log_error(error_info)
@@ -250,9 +271,7 @@ module Aidp
250
271
  tool_name = context[:tool_name] || "analysis tool"
251
272
  error_msg = "#{tool_name} failed: #{error.message}"
252
273
 
253
- if context[:installation_guide]
254
- error_msg += "\n\nTo install #{tool_name}:\n#{context[:installation_guide]}"
255
- end
274
+ error_msg += "\n\nTo install #{tool_name}:\n#{context[:installation_guide]}" if context[:installation_guide]
256
275
 
257
276
  raise AnalysisToolError.new(error_msg)
258
277
  end
@@ -80,7 +80,7 @@ module Aidp
80
80
  display_message(box)
81
81
  end
82
82
 
83
- def load_kb_data
83
+ def load_kb_data(suppress_parse_warnings: false)
84
84
  data = {}
85
85
 
86
86
  %w[symbols imports calls metrics seams hotspots tests cycles].each do |type|
@@ -89,8 +89,7 @@ module Aidp
89
89
  begin
90
90
  data[type.to_sym] = JSON.parse(File.read(file_path), symbolize_names: true)
91
91
  rescue JSON::ParserError => e
92
- # Suppress warnings in test mode to avoid CI failures
93
- unless ENV["RACK_ENV"] == "test" || defined?(RSpec)
92
+ unless suppress_parse_warnings
94
93
  display_message("Warning: Could not parse #{file_path}: #{e.message}", type: :warn)
95
94
  end
96
95
  data[type.to_sym] = []
@@ -9,9 +9,10 @@ module Aidp
9
9
  class Progress
10
10
  attr_reader :project_dir, :progress_file
11
11
 
12
- def initialize(project_dir)
12
+ def initialize(project_dir, skip_persistence: false)
13
13
  @project_dir = project_dir
14
14
  @progress_file = File.join(project_dir, ".aidp", "progress", "analyze.yml")
15
+ @skip_persistence = skip_persistence
15
16
  load_progress
16
17
  end
17
18
 
@@ -60,26 +61,20 @@ module Aidp
60
61
  private
61
62
 
62
63
  def load_progress
63
- # In test mode, only skip file operations if no progress file exists
64
- if (ENV["RACK_ENV"] == "test" || defined?(RSpec)) && !File.exist?(@progress_file)
64
+ if @skip_persistence && !File.exist?(@progress_file)
65
65
  @progress = {}
66
66
  return
67
67
  end
68
-
69
- @progress = if File.exist?(@progress_file)
70
- YAML.load_file(@progress_file) || {}
68
+ @progress = if !@skip_persistence && File.exist?(@progress_file)
69
+ YAML.safe_load_file(@progress_file, permitted_classes: [Date, Time, Symbol], aliases: true) || {}
71
70
  else
72
71
  {}
73
72
  end
74
-
75
- # Ensure @progress is never nil
76
73
  @progress = {} if @progress.nil?
77
74
  end
78
75
 
79
76
  def save_progress
80
- # In test mode, skip file operations to avoid hanging
81
- return if ENV["RACK_ENV"] == "test" || defined?(RSpec)
82
-
77
+ return if @skip_persistence
83
78
  FileUtils.mkdir_p(File.dirname(@progress_file))
84
79
  File.write(@progress_file, @progress.to_yaml)
85
80
  end
@@ -102,7 +102,7 @@ module Aidp
102
102
 
103
103
  providers.each do |provider|
104
104
  provider_info = Aidp::Harness::ProviderInfo.new(provider, @root_dir)
105
- info = provider_info.get_info
105
+ info = provider_info.info
106
106
 
107
107
  next unless info[:mcp_support]
108
108
 
data/lib/aidp/cli.rb CHANGED
@@ -147,6 +147,10 @@ module Aidp
147
147
  class << self
148
148
  extend Aidp::MessageDisplay::ClassMethods
149
149
 
150
+ def create_prompt
151
+ ::TTY::Prompt.new
152
+ end
153
+
150
154
  def run(args = ARGV)
151
155
  # Handle subcommands first (status, jobs, kb, harness)
152
156
  return run_subcommand(args) if subcommand?(args)
@@ -173,7 +177,7 @@ module Aidp
173
177
 
174
178
  # Handle configuration setup
175
179
  # Create a prompt for the wizard
176
- prompt = TTY::Prompt.new
180
+ prompt = create_prompt
177
181
 
178
182
  if options[:setup_config]
179
183
  # Force setup/reconfigure even if config exists
@@ -241,7 +245,7 @@ module Aidp
241
245
 
242
246
  if File.exist?(config_path)
243
247
  require "yaml"
244
- full_config = YAML.load_file(config_path)
248
+ full_config = YAML.safe_load_file(config_path, permitted_classes: [Date, Time, Symbol], aliases: true)
245
249
  logging_config = full_config["logging"] || full_config[:logging] || {}
246
250
  end
247
251
 
@@ -398,7 +402,7 @@ module Aidp
398
402
 
399
403
  def run_jobs_command(args = [])
400
404
  require_relative "cli/jobs_command"
401
- jobs_cmd = Aidp::CLI::JobsCommand.new(prompt: TTY::Prompt.new)
405
+ jobs_cmd = Aidp::CLI::JobsCommand.new(prompt: create_prompt)
402
406
  subcommand = args.shift
403
407
  jobs_cmd.run(subcommand, args)
404
408
  end
@@ -504,21 +508,6 @@ module Aidp
504
508
  if step
505
509
  display_message("Running #{mode} step '#{step}' with enhanced TUI harness", type: :highlight)
506
510
  display_message("progress indicators", type: :info)
507
- if step.start_with?("00_PRD") && (defined?(RSpec) || ENV["RSPEC_RUNNING"])
508
- # Simulate questions & completion similar to TUI test mode
509
- root = ENV["AIDP_ROOT"] || Dir.pwd
510
- file = Dir.glob(File.join(root, "templates", (mode == :execute) ? "EXECUTE" : "ANALYZE", "00_PRD*.md")).first
511
- if file && File.file?(file)
512
- content = File.read(file)
513
- questions_section = content.split(/## Questions/i)[1]
514
- if questions_section
515
- questions_section.lines.select { |l| l.strip.start_with?("-") }.each do |line|
516
- display_message(line.strip.sub(/^-\s*/, ""), type: :info)
517
- end
518
- end
519
- end
520
- display_message("PRD completed", type: :success)
521
- end
522
511
  return
523
512
  end
524
513
  display_message("Starting enhanced TUI harness", type: :highlight)
@@ -601,7 +590,7 @@ module Aidp
601
590
  when "clear"
602
591
  force = args.include?("--force")
603
592
  unless force
604
- prompt = TTY::Prompt.new
593
+ prompt = create_prompt
605
594
  confirm = prompt.yes?("Are you sure you want to clear all checkpoint data?")
606
595
  return unless confirm
607
596
  end
@@ -696,7 +685,7 @@ module Aidp
696
685
  end
697
686
  end
698
687
  config_manager = Aidp::Harness::ConfigManager.new(Dir.pwd)
699
- pm = Aidp::Harness::ProviderManager.new(config_manager, prompt: TTY::Prompt.new)
688
+ pm = Aidp::Harness::ProviderManager.new(config_manager, prompt: create_prompt)
700
689
 
701
690
  # Use TTY::Spinner for progress indication
702
691
  require "tty-spinner"
@@ -738,7 +727,12 @@ module Aidp
738
727
  end
739
728
  tokens = (r[:total_tokens].to_i > 0) ? r[:total_tokens].to_s : "0"
740
729
  reason = r[:unhealthy_reason] || "-"
741
- if no_color || !$stdout.tty?
730
+ is_tty = begin
731
+ $stdout.respond_to?(:tty?) && $stdout.tty?
732
+ rescue
733
+ false
734
+ end
735
+ if no_color || !is_tty
742
736
  [r[:provider], r[:status], (r[:available] ? "yes" : "no"), cb, rl, tokens, last_used, reason]
743
737
  else
744
738
  [
@@ -757,7 +751,7 @@ module Aidp
757
751
  table = TTY::Table.new header, table_rows
758
752
  display_message(table.render(:basic), type: :info)
759
753
  rescue => e
760
- log_rescue(e, component: "cli", action: "display_provider_health", fallback: "error_message")
754
+ Aidp.logger.warn("cli", "Failed to display provider health", error_class: e.class.name, error_message: e.message)
761
755
  display_message("Failed to display provider health: #{e.message}", type: :error)
762
756
  end
763
757
 
@@ -777,7 +771,7 @@ module Aidp
777
771
  display_message("=" * 60, type: :muted)
778
772
 
779
773
  provider_info = Aidp::Harness::ProviderInfo.new(provider_name, Dir.pwd)
780
- info = provider_info.get_info(force_refresh: force_refresh)
774
+ info = provider_info.info(force_refresh: force_refresh)
781
775
 
782
776
  if info.nil?
783
777
  display_message("No information available for provider: #{provider_name}", type: :error)
@@ -1044,7 +1038,7 @@ module Aidp
1044
1038
  return
1045
1039
  end
1046
1040
 
1047
- wizard = Aidp::Setup::Wizard.new(Dir.pwd, prompt: TTY::Prompt.new, dry_run: dry_run)
1041
+ wizard = Aidp::Setup::Wizard.new(Dir.pwd, prompt: create_prompt, dry_run: dry_run)
1048
1042
  wizard.run
1049
1043
  end
1050
1044
 
@@ -1071,7 +1065,7 @@ module Aidp
1071
1065
  end
1072
1066
 
1073
1067
  require_relative "init/runner"
1074
- runner = Aidp::Init::Runner.new(Dir.pwd, prompt: TTY::Prompt.new, options: options)
1068
+ runner = Aidp::Init::Runner.new(Dir.pwd, prompt: create_prompt, options: options)
1075
1069
  runner.run
1076
1070
  end
1077
1071
 
@@ -1127,7 +1121,7 @@ module Aidp
1127
1121
  project_dir: Dir.pwd,
1128
1122
  once: once,
1129
1123
  use_workstreams: use_workstreams,
1130
- prompt: TTY::Prompt.new
1124
+ prompt: create_prompt
1131
1125
  )
1132
1126
  runner.start
1133
1127
  rescue ArgumentError => e
@@ -1244,7 +1238,7 @@ module Aidp
1244
1238
 
1245
1239
  # Confirm removal unless --force
1246
1240
  unless force
1247
- prompt = TTY::Prompt.new
1241
+ prompt = create_prompt
1248
1242
  confirm = prompt.yes?("Remove workstream '#{slug}'?#{" (will also delete branch)" if delete_branch}")
1249
1243
  return unless confirm
1250
1244
  end
@@ -1722,10 +1716,10 @@ module Aidp
1722
1716
 
1723
1717
  by_source = registry.by_source
1724
1718
 
1725
- if by_source[:builtin].any?
1726
- display_message("Built-in Skills", type: :highlight)
1719
+ if by_source[:template].any?
1720
+ display_message("Template Skills", type: :highlight)
1727
1721
  display_message("=" * 80, type: :muted)
1728
- table_rows = by_source[:builtin].map do |skill_id|
1722
+ table_rows = by_source[:template].map do |skill_id|
1729
1723
  skill = registry.find(skill_id)
1730
1724
  [skill_id, skill.version, skill.description[0, 60]]
1731
1725
  end
@@ -1735,10 +1729,10 @@ module Aidp
1735
1729
  display_message("", type: :info)
1736
1730
  end
1737
1731
 
1738
- if by_source[:custom].any?
1739
- display_message("Custom Skills", type: :highlight)
1732
+ if by_source[:project].any?
1733
+ display_message("Project Skills", type: :highlight)
1740
1734
  display_message("=" * 80, type: :muted)
1741
- table_rows = by_source[:custom].map do |skill_id|
1735
+ table_rows = by_source[:project].map do |skill_id|
1742
1736
  skill = registry.find(skill_id)
1743
1737
  [skill_id, skill.version, skill.description[0, 60]]
1744
1738
  end
@@ -1846,6 +1840,204 @@ module Aidp
1846
1840
  display_message("Failed to search skills: #{e.message}", type: :error)
1847
1841
  end
1848
1842
 
1843
+ when "preview"
1844
+ # Preview full skill content
1845
+ skill_id = args.shift
1846
+
1847
+ unless skill_id
1848
+ display_message("Usage: aidp skill preview <skill-id>", type: :info)
1849
+ return
1850
+ end
1851
+
1852
+ begin
1853
+ registry = Aidp::Skills::Registry.new(project_dir: Dir.pwd)
1854
+ registry.load_skills
1855
+
1856
+ skill = registry.find(skill_id)
1857
+
1858
+ unless skill
1859
+ display_message("Skill not found: #{skill_id}", type: :error)
1860
+ display_message("Use 'aidp skill list' to see available skills", type: :muted)
1861
+ return
1862
+ end
1863
+
1864
+ require_relative "skills/wizard/builder"
1865
+ require_relative "skills/wizard/template_library"
1866
+
1867
+ builder = Aidp::Skills::Wizard::Builder.new
1868
+ full_content = builder.to_skill_md(skill)
1869
+
1870
+ # Check if this is a project skill with a matching template
1871
+ source_info = registry.by_source[skill_id]
1872
+ inheritance_info = ""
1873
+ if source_info == :project
1874
+ template_library = Aidp::Skills::Wizard::TemplateLibrary.new(project_dir: Dir.pwd)
1875
+ template_skill = template_library.templates.find { |s| s.id == skill.id }
1876
+ if template_skill
1877
+ inheritance_info = " (inherits from template)"
1878
+ end
1879
+ elsif source_info == :template
1880
+ inheritance_info = " (template)"
1881
+ end
1882
+
1883
+ display_message("\n" + "=" * 60, type: :info)
1884
+ display_message("Skill: #{skill.name} (#{skill.id}) v#{skill.version}#{inheritance_info}", type: :highlight)
1885
+ display_message("=" * 60 + "\n", type: :info)
1886
+ display_message(full_content, type: :info)
1887
+ display_message("\n" + "=" * 60, type: :info)
1888
+ rescue => e
1889
+ display_message("Failed to preview skill: #{e.message}", type: :error)
1890
+ end
1891
+
1892
+ when "diff"
1893
+ # Show diff between project skill and template
1894
+ skill_id = args.shift
1895
+
1896
+ unless skill_id
1897
+ display_message("Usage: aidp skill diff <skill-id>", type: :info)
1898
+ return
1899
+ end
1900
+
1901
+ begin
1902
+ require_relative "skills/wizard/template_library"
1903
+ require_relative "skills/wizard/differ"
1904
+
1905
+ registry = Aidp::Skills::Registry.new(project_dir: Dir.pwd)
1906
+ registry.load_skills
1907
+
1908
+ project_skill = registry.find(skill_id)
1909
+
1910
+ unless project_skill
1911
+ display_message("Skill not found: #{skill_id}", type: :error)
1912
+ return
1913
+ end
1914
+
1915
+ # Check if it's a project skill
1916
+ unless registry.by_source[:project].include?(skill_id)
1917
+ display_message("Skill '#{skill_id}' is a template skill, not a project skill", type: :info)
1918
+ display_message("Only project skills can be diffed against templates", type: :muted)
1919
+ return
1920
+ end
1921
+
1922
+ # Find the template
1923
+ template_library = Aidp::Skills::Wizard::TemplateLibrary.new(project_dir: Dir.pwd)
1924
+ template_skill = template_library.find(skill_id)
1925
+
1926
+ unless template_skill
1927
+ display_message("No template found for skill '#{skill_id}'", type: :info)
1928
+ display_message("This is a custom skill without a template base", type: :muted)
1929
+ return
1930
+ end
1931
+
1932
+ # Show diff
1933
+ differ = Aidp::Skills::Wizard::Differ.new
1934
+ diff_result = differ.diff(template_skill, project_skill)
1935
+ differ.display(diff_result)
1936
+ rescue => e
1937
+ display_message("Failed to diff skill: #{e.message}", type: :error)
1938
+ end
1939
+
1940
+ when "edit"
1941
+ # Edit an existing skill
1942
+ skill_id = args.shift
1943
+
1944
+ unless skill_id
1945
+ display_message("Usage: aidp skill edit <skill-id>", type: :info)
1946
+ return
1947
+ end
1948
+
1949
+ begin
1950
+ require_relative "skills/wizard/controller"
1951
+
1952
+ registry = Aidp::Skills::Registry.new(project_dir: Dir.pwd)
1953
+ registry.load_skills
1954
+
1955
+ skill = registry.find(skill_id)
1956
+
1957
+ unless skill
1958
+ display_message("Skill not found: #{skill_id}", type: :error)
1959
+ display_message("Use 'aidp skill list' to see available skills", type: :muted)
1960
+ return
1961
+ end
1962
+
1963
+ # Check if it's editable (must be project skill or willing to copy template)
1964
+ if registry.by_source[:template].include?(skill_id)
1965
+ display_message("'#{skill_id}' is a template skill", type: :info)
1966
+ display_message("Editing will create a project override in .aidp/skills/", type: :muted)
1967
+ end
1968
+
1969
+ # Parse options
1970
+ options = {}
1971
+ while args.first&.start_with?("--")
1972
+ opt = args.shift
1973
+ case opt
1974
+ when "--dry-run"
1975
+ options[:dry_run] = true
1976
+ when "--open-editor"
1977
+ options[:open_editor] = true
1978
+ else
1979
+ display_message("Unknown option: #{opt}", type: :error)
1980
+ return
1981
+ end
1982
+ end
1983
+
1984
+ # Pre-fill wizard with existing skill data
1985
+ options[:id] = skill.id
1986
+ options[:name] = skill.name
1987
+ options[:edit_mode] = true
1988
+ options[:existing_skill] = skill
1989
+
1990
+ # Run wizard in edit mode
1991
+ wizard = Aidp::Skills::Wizard::Controller.new(
1992
+ project_dir: Dir.pwd,
1993
+ options: options
1994
+ )
1995
+ wizard.run
1996
+ rescue => e
1997
+ display_message("Failed to edit skill: #{e.message}", type: :error)
1998
+ end
1999
+
2000
+ when "new"
2001
+ # Create a new skill using the wizard
2002
+ begin
2003
+ require_relative "skills/wizard/controller"
2004
+
2005
+ # Parse options
2006
+ options = {}
2007
+ while args.first&.start_with?("--")
2008
+ opt = args.shift
2009
+ case opt
2010
+ when "--minimal"
2011
+ options[:minimal] = true
2012
+ when "--dry-run"
2013
+ options[:dry_run] = true
2014
+ when "--yes", "-y"
2015
+ options[:yes] = true
2016
+ when "--id"
2017
+ options[:id] = args.shift
2018
+ when "--name"
2019
+ options[:name] = args.shift
2020
+ when "--from-template"
2021
+ options[:from_template] = args.shift
2022
+ when "--clone"
2023
+ options[:clone] = args.shift
2024
+ else
2025
+ display_message("Unknown option: #{opt}", type: :error)
2026
+ return
2027
+ end
2028
+ end
2029
+
2030
+ # Run wizard
2031
+ wizard = Aidp::Skills::Wizard::Controller.new(
2032
+ project_dir: Dir.pwd,
2033
+ options: options
2034
+ )
2035
+ wizard.run
2036
+ rescue => e
2037
+ display_message("Failed to create skill: #{e.message}", type: :error)
2038
+ Aidp.log_error("cli", "Skill wizard failed", error: e.message, backtrace: e.backtrace.first(5))
2039
+ end
2040
+
1849
2041
  when "validate"
1850
2042
  # Validate skill file format
1851
2043
  skill_path = args.shift
@@ -1893,19 +2085,94 @@ module Aidp
1893
2085
  end
1894
2086
  end
1895
2087
 
2088
+ when "delete"
2089
+ # Delete a project skill
2090
+ skill_id = args.shift
2091
+
2092
+ unless skill_id
2093
+ display_message("Usage: aidp skill delete <skill-id>", type: :info)
2094
+ return
2095
+ end
2096
+
2097
+ begin
2098
+ registry = Aidp::Skills::Registry.new(project_dir: Dir.pwd)
2099
+ registry.load_skills
2100
+
2101
+ skill = registry.find(skill_id)
2102
+
2103
+ unless skill
2104
+ display_message("Skill not found: #{skill_id}", type: :error)
2105
+ return
2106
+ end
2107
+
2108
+ # Check if it's a project skill
2109
+ source = registry.by_source[skill_id]
2110
+ unless source == :project
2111
+ display_message("Cannot delete template skill '#{skill_id}'", type: :error)
2112
+ display_message("Only project skills in .aidp/skills/ can be deleted", type: :muted)
2113
+ return
2114
+ end
2115
+
2116
+ # Get skill directory
2117
+ skill_dir = File.dirname(skill.source_path)
2118
+
2119
+ # Confirm deletion
2120
+ require "tty-prompt"
2121
+ prompt = create_prompt
2122
+ confirmed = prompt.yes?("Delete skill '#{skill.name}' (#{skill_id})? This cannot be undone.")
2123
+
2124
+ unless confirmed
2125
+ display_message("Deletion cancelled", type: :info)
2126
+ return
2127
+ end
2128
+
2129
+ # Delete the skill directory
2130
+ require "fileutils"
2131
+ FileUtils.rm_rf(skill_dir)
2132
+
2133
+ display_message("✓ Deleted skill: #{skill.name} (#{skill_id})", type: :success)
2134
+ rescue => e
2135
+ display_message("Failed to delete skill: #{e.message}", type: :error)
2136
+ Aidp.log_error("cli", "Skill deletion failed", error: e.message, backtrace: e.backtrace.first(5))
2137
+ end
2138
+
1896
2139
  else
1897
2140
  display_message("Usage: aidp skill <command>", type: :info)
1898
2141
  display_message("", type: :info)
1899
2142
  display_message("Commands:", type: :info)
1900
2143
  display_message(" list List all available skills (default)", type: :info)
1901
2144
  display_message(" show <id> Show detailed skill information", type: :info)
2145
+ display_message(" preview <id> Preview full SKILL.md content", type: :info)
2146
+ display_message(" diff <id> Show diff between project skill and template", type: :info)
1902
2147
  display_message(" search <query> Search skills by keyword", type: :info)
2148
+ display_message(" new [options] Create a new skill using the wizard", type: :info)
2149
+ display_message(" edit <id> [options] Edit an existing skill", type: :info)
2150
+ display_message(" delete <id> Delete a project skill", type: :info)
1903
2151
  display_message(" validate [path] Validate skill file format", type: :info)
1904
2152
  display_message("", type: :info)
2153
+ display_message("New Skill Options:", type: :info)
2154
+ display_message(" --minimal Skip optional sections", type: :info)
2155
+ display_message(" --dry-run Preview without saving", type: :info)
2156
+ display_message(" --yes, -y Skip confirmation prompts", type: :info)
2157
+ display_message(" --id <skill_id> Pre-set skill ID", type: :info)
2158
+ display_message(" --name <name> Pre-set skill name", type: :info)
2159
+ display_message("", type: :info)
2160
+ display_message("Edit Skill Options:", type: :info)
2161
+ display_message(" --dry-run Preview changes without saving", type: :info)
2162
+ display_message(" --open-editor Open content in $EDITOR", type: :info)
2163
+ display_message("", type: :info)
1905
2164
  display_message("Examples:", type: :info)
1906
2165
  display_message(" aidp skill list # List all skills", type: :info)
1907
2166
  display_message(" aidp skill show repository_analyst # Show skill details", type: :info)
2167
+ display_message(" aidp skill preview repository_analyst # Preview full content", type: :info)
2168
+ display_message(" aidp skill diff my_skill # Show diff with template", type: :info)
1908
2169
  display_message(" aidp skill search git # Search for git-related skills", type: :info)
2170
+ display_message(" aidp skill new # Create new skill (interactive)", type: :info)
2171
+ display_message(" aidp skill new --minimal --id my_skill # Create with minimal prompts", type: :info)
2172
+ display_message(" aidp skill new --from-template repo_analyst # Inherit from template", type: :info)
2173
+ display_message(" aidp skill new --clone my_existing_skill # Clone existing skill", type: :info)
2174
+ display_message(" aidp skill edit repository_analyst # Edit existing skill", type: :info)
2175
+ display_message(" aidp skill delete my_custom_skill # Delete a project skill", type: :info)
1909
2176
  display_message(" aidp skill validate skills/my_skill/SKILL.md # Validate specific skill", type: :info)
1910
2177
  display_message(" aidp skill validate # Validate all skills", type: :info)
1911
2178
  end
data/lib/aidp/config.rb CHANGED
@@ -312,7 +312,7 @@ module Aidp
312
312
  end
313
313
 
314
314
  private_class_method def self.load_yaml_config(config_file)
315
- YAML.load_file(config_file) || {}
315
+ YAML.safe_load_file(config_file, permitted_classes: [Date, Time, Symbol], aliases: true) || {}
316
316
  rescue => e
317
317
  warn "Failed to load configuration file #{config_file}: #{e.message}"
318
318
  {}
@@ -26,6 +26,7 @@ module Aidp
26
26
  @provider_manager = provider_manager
27
27
  @config = config
28
28
  @options = options
29
+ @cancel_timeout = options[:cancel_timeout] || 5 # seconds to wait for graceful shutdown
29
30
  @state = WorkLoopState.new
30
31
  @instruction_queue = InstructionQueue.new
31
32
  @work_thread = nil
@@ -92,7 +93,7 @@ module Aidp
92
93
  @state.append_output("Cancellation requested, waiting for safe stopping point...", type: :warning)
93
94
 
94
95
  # Wait for thread to notice cancellation
95
- @work_thread&.join(5) # 5 second timeout
96
+ @work_thread&.join(@cancel_timeout)
96
97
 
97
98
  if save_checkpoint && @sync_runner
98
99
  @state.append_output("Saving checkpoint before exit...", type: :info)