aidp 0.16.0 → 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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9bbab73392b0bc0fdcd74bb0f0e0bbf09716b2d2451b04aaa64c4ca4ccc7638e
4
- data.tar.gz: af0d8b6d8937a1781b3b379ab6328ff17c5f0c96ea00c80f5886ce15b2265e2f
3
+ metadata.gz: 84451cdcc0886f1a70e799ab53d9b8898633f324cdd80c5f07a5f204848a3de2
4
+ data.tar.gz: e93982382c700564db24fac6ff1de2276e5b0301b91f6cade19aa8dfacd3f7d3
5
5
  SHA512:
6
- metadata.gz: ee40afa8126a8f8d6f92a7df6fbc634c6eb054499bb7dfdec53946f05ecf8faf8c1bcae1eb02274a7441bdd7685751363e20207587df818c8bb8fe300bfbc50f
7
- data.tar.gz: 010a5afdd1a06905f09a423efe3bfde2ef84aca498f939667871191b1c9174168c31807959cbc3d50e7f7ab2cc552301032c1d232f6e70331ee8918debb7aba5
6
+ metadata.gz: 3de783cb25c3eedc9056f77c2b294918a2f5ab46c009280897b6094dd157c45c681fc5c8fe22c6eea401ba74e0c4151c132a85e3c8ceedfef7722e9580f96f43
7
+ data.tar.gz: de86c77e08a8f9b837d501d8e69786b6f969c0e2a1538f1a9d2062267f01d6e8c855f0885c31d0bdc3f2c9362fa78439fccdcfe5e09a12cd1ab61282365cdc4a
@@ -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
@@ -67,7 +67,7 @@ module Aidp
67
67
  end
68
68
 
69
69
  @progress = if File.exist?(@progress_file)
70
- YAML.load_file(@progress_file) || {}
70
+ YAML.safe_load_file(@progress_file, permitted_classes: [Date, Time, Symbol], aliases: true) || {}
71
71
  else
72
72
  {}
73
73
  end
data/lib/aidp/cli.rb CHANGED
@@ -241,7 +241,7 @@ module Aidp
241
241
 
242
242
  if File.exist?(config_path)
243
243
  require "yaml"
244
- full_config = YAML.load_file(config_path)
244
+ full_config = YAML.safe_load_file(config_path, permitted_classes: [Date, Time, Symbol], aliases: true)
245
245
  logging_config = full_config["logging"] || full_config[:logging] || {}
246
246
  end
247
247
 
@@ -1722,10 +1722,10 @@ module Aidp
1722
1722
 
1723
1723
  by_source = registry.by_source
1724
1724
 
1725
- if by_source[:builtin].any?
1726
- display_message("Built-in Skills", type: :highlight)
1725
+ if by_source[:template].any?
1726
+ display_message("Template Skills", type: :highlight)
1727
1727
  display_message("=" * 80, type: :muted)
1728
- table_rows = by_source[:builtin].map do |skill_id|
1728
+ table_rows = by_source[:template].map do |skill_id|
1729
1729
  skill = registry.find(skill_id)
1730
1730
  [skill_id, skill.version, skill.description[0, 60]]
1731
1731
  end
@@ -1735,10 +1735,10 @@ module Aidp
1735
1735
  display_message("", type: :info)
1736
1736
  end
1737
1737
 
1738
- if by_source[:custom].any?
1739
- display_message("Custom Skills", type: :highlight)
1738
+ if by_source[:project].any?
1739
+ display_message("Project Skills", type: :highlight)
1740
1740
  display_message("=" * 80, type: :muted)
1741
- table_rows = by_source[:custom].map do |skill_id|
1741
+ table_rows = by_source[:project].map do |skill_id|
1742
1742
  skill = registry.find(skill_id)
1743
1743
  [skill_id, skill.version, skill.description[0, 60]]
1744
1744
  end
@@ -1846,6 +1846,204 @@ module Aidp
1846
1846
  display_message("Failed to search skills: #{e.message}", type: :error)
1847
1847
  end
1848
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
+
1849
2047
  when "validate"
1850
2048
  # Validate skill file format
1851
2049
  skill_path = args.shift
@@ -1893,19 +2091,94 @@ module Aidp
1893
2091
  end
1894
2092
  end
1895
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
+
1896
2145
  else
1897
2146
  display_message("Usage: aidp skill <command>", type: :info)
1898
2147
  display_message("", type: :info)
1899
2148
  display_message("Commands:", type: :info)
1900
2149
  display_message(" list List all available skills (default)", type: :info)
1901
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)
1902
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)
1903
2157
  display_message(" validate [path] Validate skill file format", type: :info)
1904
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)
1905
2170
  display_message("Examples:", type: :info)
1906
2171
  display_message(" aidp skill list # List all skills", type: :info)
1907
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)
1908
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)
1909
2182
  display_message(" aidp skill validate skills/my_skill/SKILL.md # Validate specific skill", type: :info)
1910
2183
  display_message(" aidp skill validate # Validate all skills", type: :info)
1911
2184
  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)
@@ -40,7 +40,7 @@ module Aidp
40
40
  # Get the latest checkpoint data
41
41
  def latest_checkpoint
42
42
  return nil unless File.exist?(@checkpoint_file)
43
- YAML.load_file(@checkpoint_file)
43
+ YAML.safe_load_file(@checkpoint_file, permitted_classes: [Date, Time, Symbol], aliases: true)
44
44
  end
45
45
 
46
46
  # Get checkpoint history for analysis
@@ -218,7 +218,7 @@ module Aidp
218
218
  def load_existing_backlog
219
219
  return unless File.exist?(@backlog_file)
220
220
 
221
- data = YAML.load_file(@backlog_file)
221
+ data = YAML.safe_load_file(@backlog_file, permitted_classes: [Date, Time, Symbol], aliases: true)
222
222
  @entries = data["entries"] || [] if data.is_a?(Hash)
223
223
  @entries = symbolize_keys_deep(@entries)
224
224
  rescue => e
@@ -68,7 +68,7 @@ module Aidp
68
68
  end
69
69
 
70
70
  @progress = if File.exist?(@progress_file)
71
- YAML.load_file(@progress_file) || {}
71
+ YAML.safe_load_file(@progress_file, permitted_classes: [Date, Time, Symbol], aliases: true) || {}
72
72
  else
73
73
  {}
74
74
  end
@@ -9,7 +9,7 @@ module Aidp
9
9
  # - /split - Divide work into smaller contracts
10
10
  # - /halt-on <pattern> - Pause on specific test failures
11
11
  class ReplMacros
12
- attr_reader :pinned_files, :focus_patterns, :halt_patterns, :split_mode, :current_workstream
12
+ attr_reader :pinned_files, :focus_patterns, :halt_patterns, :split_mode, :current_workstream, :current_skill
13
13
 
14
14
  def initialize(project_dir: Dir.pwd)
15
15
  @pinned_files = Set.new
@@ -18,6 +18,7 @@ module Aidp
18
18
  @split_mode = false
19
19
  @project_dir = project_dir
20
20
  @current_workstream = nil
21
+ @current_skill = nil
21
22
  @commands = register_commands
22
23
  end
23
24
 
@@ -65,6 +66,7 @@ module Aidp
65
66
  halt_patterns: @halt_patterns,
66
67
  split_mode: @split_mode,
67
68
  current_workstream: @current_workstream,
69
+ current_skill: @current_skill,
68
70
  active_constraints: active_constraints_count
69
71
  }
70
72
  end
@@ -113,6 +115,30 @@ module Aidp
113
115
  true
114
116
  end
115
117
 
118
+ # Retrieve the current skill object, or nil if none is selected
119
+ #
120
+ # This method provides access to the full skill object (with content, providers, etc.)
121
+ # for the currently selected skill via `/skill use <id>`.
122
+ #
123
+ # @return [Aidp::Skills::Skill, nil] The current skill object or nil
124
+ #
125
+ # @example
126
+ # repl = ReplMacros.new(project_dir: Dir.pwd)
127
+ # repl.execute("/skill use repository_analyst")
128
+ # skill = repl.current_skill_object
129
+ # puts skill.content if skill # => skill's markdown content
130
+ def current_skill_object
131
+ return nil unless @current_skill
132
+
133
+ require_relative "../skills"
134
+ registry = Aidp::Skills::Registry.new(project_dir: @project_dir)
135
+ registry.load_skills
136
+ registry.find(@current_skill)
137
+ rescue => e
138
+ Aidp.log_error("repl_macros", "Failed to load current skill object", error: e.message)
139
+ nil
140
+ end
141
+
116
142
  private
117
143
 
118
144
  # Register all available REPL commands
@@ -1257,25 +1283,25 @@ module Aidp
1257
1283
  lines = ["Available Skills:", ""]
1258
1284
  by_source = registry.by_source
1259
1285
 
1260
- if by_source[:builtin].any?
1261
- lines << "Built-in Skills:"
1262
- by_source[:builtin].each do |skill_id|
1286
+ if by_source[:template].any?
1287
+ lines << "Template Skills:"
1288
+ by_source[:template].each do |skill_id|
1263
1289
  skill = registry.find(skill_id)
1264
1290
  lines << " • #{skill_id} - #{skill.description}"
1265
1291
  end
1266
1292
  lines << ""
1267
1293
  end
1268
1294
 
1269
- if by_source[:custom].any?
1270
- lines << "Custom Skills:"
1271
- by_source[:custom].each do |skill_id|
1295
+ if by_source[:project].any?
1296
+ lines << "Project Skills:"
1297
+ by_source[:project].each do |skill_id|
1272
1298
  skill = registry.find(skill_id)
1273
- lines << " • #{skill_id} - #{skill.description} [CUSTOM]"
1299
+ lines << " • #{skill_id} - #{skill.description} [PROJECT]"
1274
1300
  end
1275
1301
  lines << ""
1276
1302
  end
1277
1303
 
1278
- lines << "Use '/skill show <id>' for details"
1304
+ lines << "Use '/skill show <id>' for details or '/skill use <id>' to activate"
1279
1305
 
1280
1306
  {
1281
1307
  success: true,
@@ -1412,10 +1438,53 @@ module Aidp
1412
1438
  }
1413
1439
  end
1414
1440
 
1441
+ when "use"
1442
+ # Switch to a specific skill
1443
+ skill_id = args.shift
1444
+
1445
+ unless skill_id
1446
+ return {
1447
+ success: false,
1448
+ message: "Usage: /skill use <skill-id>",
1449
+ action: :none
1450
+ }
1451
+ end
1452
+
1453
+ begin
1454
+ registry = Aidp::Skills::Registry.new(project_dir: @project_dir)
1455
+ registry.load_skills
1456
+
1457
+ skill = registry.find(skill_id)
1458
+
1459
+ unless skill
1460
+ return {
1461
+ success: false,
1462
+ message: "Skill not found: #{skill_id}\nUse '/skill list' to see available skills",
1463
+ action: :none
1464
+ }
1465
+ end
1466
+
1467
+ # Store the current skill for the session
1468
+ @current_skill = skill_id
1469
+
1470
+ {
1471
+ success: true,
1472
+ message: "✓ Now using skill: #{skill.name} (#{skill_id})\n\n#{skill.description}",
1473
+ action: :switch_skill,
1474
+ data: {skill_id: skill_id, skill: skill}
1475
+ }
1476
+ rescue => e
1477
+ {
1478
+ success: false,
1479
+ message: "Failed to switch skill: #{e.message}",
1480
+ action: :none
1481
+ }
1482
+ end
1483
+
1415
1484
  else
1416
1485
  {
1417
1486
  success: false,
1418
- message: "Usage: /skill <command> [args]\n\nCommands:\n list - List all available skills\n show <id> - Show detailed skill information\n search <query> - Search skills by keyword\n\nExamples:\n /skill list\n /skill show repository_analyst\n /skill search git",
1487
+ message: "Usage: /skill <command> [args]\n\nCommands:\n list - List all available skills\n show <id> - Show detailed skill information\n search <query> - Search skills by keyword\n use <id> - Switch to a specific skill\n\nExamples:\n /skill list\n /skill show repository_analyst\n /skill search git\n /skill use repository_analyst",
1419
1488
  action: :none
1420
1489
  }
1421
1490
  end
@@ -256,7 +256,7 @@ module Aidp
256
256
  return unless @config_file
257
257
 
258
258
  begin
259
- @config = YAML.load_file(@config_file) || {}
259
+ @config = YAML.safe_load_file(@config_file, permitted_classes: [Date, Time, Symbol], aliases: true) || {}
260
260
  rescue => e
261
261
  @config = {}
262
262
  @validation_result = {
@@ -213,7 +213,7 @@ module Aidp
213
213
  return nil unless File.exist?(metadata_file)
214
214
 
215
215
  # Return raw metadata with times as ISO8601 strings to avoid unsafe class loading
216
- YAML.load_file(metadata_file)
216
+ YAML.safe_load_file(metadata_file, permitted_classes: [Date, Time, Symbol], aliases: true)
217
217
  rescue
218
218
  nil
219
219
  end