aidp 0.32.0 → 0.33.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/lib/aidp/analyze/feature_analyzer.rb +322 -320
  3. data/lib/aidp/auto_update/coordinator.rb +97 -7
  4. data/lib/aidp/auto_update.rb +0 -12
  5. data/lib/aidp/cli/devcontainer_commands.rb +0 -5
  6. data/lib/aidp/cli.rb +2 -1
  7. data/lib/aidp/comment_consolidator.rb +78 -0
  8. data/lib/aidp/concurrency.rb +0 -3
  9. data/lib/aidp/config.rb +0 -1
  10. data/lib/aidp/config_paths.rb +71 -0
  11. data/lib/aidp/execute/work_loop_runner.rb +324 -15
  12. data/lib/aidp/harness/ai_filter_factory.rb +285 -0
  13. data/lib/aidp/harness/config_schema.rb +97 -1
  14. data/lib/aidp/harness/config_validator.rb +1 -1
  15. data/lib/aidp/harness/configuration.rb +61 -5
  16. data/lib/aidp/harness/filter_definition.rb +212 -0
  17. data/lib/aidp/harness/generated_filter_strategy.rb +197 -0
  18. data/lib/aidp/harness/output_filter.rb +50 -25
  19. data/lib/aidp/harness/output_filter_config.rb +129 -0
  20. data/lib/aidp/harness/provider_manager.rb +90 -2
  21. data/lib/aidp/harness/runner.rb +0 -11
  22. data/lib/aidp/harness/test_runner.rb +179 -41
  23. data/lib/aidp/harness/thinking_depth_manager.rb +16 -0
  24. data/lib/aidp/harness/ui/navigation/submenu.rb +0 -2
  25. data/lib/aidp/loader.rb +195 -0
  26. data/lib/aidp/metadata/compiler.rb +29 -17
  27. data/lib/aidp/metadata/query.rb +1 -1
  28. data/lib/aidp/metadata/scanner.rb +8 -1
  29. data/lib/aidp/metadata/tool_metadata.rb +13 -13
  30. data/lib/aidp/metadata/validator.rb +10 -0
  31. data/lib/aidp/metadata.rb +16 -0
  32. data/lib/aidp/pr_worktree_manager.rb +2 -2
  33. data/lib/aidp/provider_manager.rb +1 -7
  34. data/lib/aidp/setup/wizard.rb +279 -9
  35. data/lib/aidp/skills.rb +0 -5
  36. data/lib/aidp/storage/csv_storage.rb +3 -0
  37. data/lib/aidp/style_guide/selector.rb +360 -0
  38. data/lib/aidp/tooling_detector.rb +283 -16
  39. data/lib/aidp/version.rb +1 -1
  40. data/lib/aidp/watch/change_request_processor.rb +152 -14
  41. data/lib/aidp/watch/repository_client.rb +41 -0
  42. data/lib/aidp/watch/runner.rb +29 -18
  43. data/lib/aidp/watch.rb +5 -7
  44. data/lib/aidp/workstream_cleanup.rb +0 -2
  45. data/lib/aidp/workstream_executor.rb +0 -4
  46. data/lib/aidp/worktree.rb +0 -1
  47. data/lib/aidp.rb +21 -106
  48. metadata +72 -36
  49. data/lib/aidp/config/paths.rb +0 -131
@@ -0,0 +1,195 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "zeitwerk"
4
+
5
+ module Aidp
6
+ # Zeitwerk-based class loader with hot code reloading support
7
+ #
8
+ # This module configures Zeitwerk for autoloading AIDP classes and provides
9
+ # a reload capability similar to Rails development mode. When files change
10
+ # (e.g., after git pull in watch mode), calling Aidp::Loader.reload! will
11
+ # unload all classes and allow them to be reloaded on next reference.
12
+ #
13
+ # @example Enable reloading in development
14
+ # Aidp::Loader.setup(enable_reloading: true)
15
+ # # ... code changes on disk ...
16
+ # Aidp::Loader.reload!
17
+ #
18
+ # @example Production mode (no reloading)
19
+ # Aidp::Loader.setup(enable_reloading: false)
20
+ # Aidp::Loader.eager_load!
21
+ module Loader
22
+ class << self
23
+ # @return [Zeitwerk::Loader, nil] The configured loader instance
24
+ attr_reader :loader
25
+
26
+ # @return [Boolean] Whether reloading is enabled
27
+ attr_reader :reloading_enabled
28
+
29
+ # Set up the Zeitwerk loader for AIDP
30
+ #
31
+ # @param enable_reloading [Boolean] Whether to enable hot reloading
32
+ # @param eager_load [Boolean] Whether to eager load all classes
33
+ # @return [Zeitwerk::Loader] The configured loader
34
+ def setup(enable_reloading: false, eager_load: false)
35
+ return @loader if @loader
36
+
37
+ Aidp.log_debug("loader", "setup_started",
38
+ enable_reloading: enable_reloading,
39
+ eager_load: eager_load)
40
+
41
+ @reloading_enabled = enable_reloading
42
+ @loader = create_loader
43
+ configure_inflections(@loader)
44
+ configure_ignores(@loader)
45
+
46
+ if enable_reloading
47
+ @loader.enable_reloading
48
+ Aidp.log_debug("loader", "reloading_enabled")
49
+ end
50
+
51
+ @loader.setup
52
+
53
+ if eager_load && !enable_reloading
54
+ @loader.eager_load
55
+ Aidp.log_debug("loader", "eager_load_complete")
56
+ end
57
+
58
+ Aidp.log_info("loader", "setup_complete",
59
+ reloading: enable_reloading,
60
+ eager_loaded: eager_load && !enable_reloading)
61
+
62
+ @loader
63
+ end
64
+
65
+ # Reload all autoloaded classes
66
+ #
67
+ # This unloads all classes managed by Zeitwerk and allows them to be
68
+ # reloaded on next reference. Only works if enable_reloading was true
69
+ # during setup.
70
+ #
71
+ # @return [Boolean] Whether reload was performed
72
+ def reload!
73
+ unless @loader
74
+ Aidp.log_warn("loader", "reload_skipped", reason: "loader_not_setup")
75
+ return false
76
+ end
77
+
78
+ unless @reloading_enabled
79
+ Aidp.log_warn("loader", "reload_skipped", reason: "reloading_disabled")
80
+ return false
81
+ end
82
+
83
+ Aidp.log_info("loader", "reload_started")
84
+
85
+ begin
86
+ @loader.reload
87
+ Aidp.log_info("loader", "reload_complete")
88
+ true
89
+ rescue => e
90
+ Aidp.log_error("loader", "reload_failed", error: e.message)
91
+ false
92
+ end
93
+ end
94
+
95
+ # Check if loader is set up for reloading
96
+ #
97
+ # @return [Boolean]
98
+ def reloading?
99
+ @reloading_enabled == true
100
+ end
101
+
102
+ # Check if loader is set up
103
+ #
104
+ # @return [Boolean]
105
+ def setup?
106
+ !@loader.nil?
107
+ end
108
+
109
+ # Eager load all classes (production mode)
110
+ #
111
+ # @return [void]
112
+ def eager_load!
113
+ @loader&.eager_load
114
+ end
115
+
116
+ # Reset the loader (mainly for testing)
117
+ #
118
+ # @return [void]
119
+ def reset!
120
+ if @loader
121
+ begin
122
+ @loader.unload if @reloading_enabled
123
+ rescue Zeitwerk::SetupRequired
124
+ # If loader was never set up, skip unload
125
+ end
126
+ @loader.unregister
127
+ end
128
+ @loader = nil
129
+ @reloading_enabled = false
130
+ end
131
+
132
+ private
133
+
134
+ def create_loader
135
+ loader = Zeitwerk::Loader.new
136
+ loader.tag = "aidp"
137
+
138
+ # Set the root directory for autoloading
139
+ lib_path = File.expand_path("..", __dir__)
140
+ loader.push_dir(lib_path, namespace: Object)
141
+
142
+ loader
143
+ end
144
+
145
+ # Configure inflections for non-standard class names
146
+ def configure_inflections(loader)
147
+ loader.inflector.inflect(
148
+ # AI-prefixed classes
149
+ "ai_decision_engine" => "AIDecisionEngine",
150
+ "ai_filter_factory" => "AIFilterFactory",
151
+
152
+ # Acronym-based names
153
+ "kb_inspector" => "KBInspector",
154
+ "ui_state" => "UIState",
155
+ "ui_error" => "UIError",
156
+ "rspec_filter_strategy" => "RSpecFilterStrategy",
157
+
158
+ # TTY-related
159
+ "tui" => "TUI",
160
+ "enhanced_tui" => "EnhancedTUI",
161
+
162
+ # Other acronyms
163
+ "pr_worktree_manager" => "PRWorktreeManager",
164
+ "csv_storage" => "CSVStorage",
165
+ "cli" => "CLI",
166
+ "ruby_llm_registry" => "RubyLLMRegistry",
167
+ "rubygems_api_adapter" => "RubyGemsAPIAdapter",
168
+ "submenu" => "SubMenu",
169
+ "terminal_io" => "TerminalIO",
170
+
171
+ # Module folders
172
+ "ui" => "UI"
173
+ )
174
+ end
175
+
176
+ # Configure files/directories to ignore
177
+ def configure_ignores(loader)
178
+ # Ignore files that are manually required before Zeitwerk
179
+ loader.ignore(File.expand_path("version.rb", __dir__))
180
+ loader.ignore(File.expand_path("core_ext", __dir__))
181
+ loader.ignore(File.expand_path("logger.rb", __dir__))
182
+ loader.ignore(File.expand_path("cli/issue_importer.rb", __dir__))
183
+ loader.ignore(File.expand_path("config/paths.rb", __dir__))
184
+
185
+ # Ignore the loader itself
186
+ loader.ignore(__FILE__)
187
+
188
+ # Ignore files with multiple constants (require manually after setup)
189
+ loader.ignore(File.expand_path("auto_update/errors.rb", __dir__))
190
+ loader.ignore(File.expand_path("errors.rb", __dir__))
191
+ loader.ignore(File.expand_path("harness/state/errors.rb", __dir__))
192
+ end
193
+ end
194
+ end
195
+ end
@@ -39,7 +39,7 @@ module Aidp
39
39
  Aidp.log_info("metadata", "Compiling tool directory", directories: @directories, output: output_path)
40
40
 
41
41
  # Scan all directories
42
- scanner = Scanner.new(@directories)
42
+ scanner = Scanner.new(@directories, strict: @strict)
43
43
  @tools = scanner.scan_all
44
44
 
45
45
  # Validate tools
@@ -47,14 +47,24 @@ module Aidp
47
47
  validation_results = validator.validate_all
48
48
 
49
49
  # Handle validation failures
50
- handle_validation_results(validation_results)
50
+ parse_error_results = scanner.parse_errors.map do |err|
51
+ Validator::ValidationResult.new(
52
+ tool_id: "(unknown)",
53
+ file_path: err[:file],
54
+ valid: false,
55
+ errors: [err[:error]],
56
+ warnings: []
57
+ )
58
+ end
59
+
60
+ invalid_results = handle_validation_results(validation_results + parse_error_results)
51
61
 
52
62
  # Build indexes and graphs
53
63
  build_indexes
54
64
  build_dependency_graph
55
65
 
56
66
  # Create directory structure
57
- directory = create_directory_structure
67
+ directory = create_directory_structure(invalid_results: invalid_results)
58
68
 
59
69
  # Write to file
60
70
  write_directory(directory, output_path)
@@ -142,22 +152,23 @@ module Aidp
142
152
  # Create directory structure for serialization
143
153
  #
144
154
  # @return [Hash] Directory structure
145
- def create_directory_structure
155
+ def create_directory_structure(invalid_results: [])
146
156
  {
147
- version: "1.0.0",
148
- compiled_at: Time.now.iso8601,
149
- tools: @tools.map(&:to_h),
150
- indexes: {
151
- by_type: @indexes[:by_type].transform_values { |tools| tools.map(&:id) },
152
- by_tag: @indexes[:by_tag].transform_values { |tools| tools.map(&:id) },
153
- by_work_unit_type: @indexes[:by_work_unit_type].transform_values { |tools| tools.map(&:id) }
157
+ "version" => "1.0.0",
158
+ "compiled_at" => Time.now.iso8601,
159
+ "tools" => @tools.map(&:to_h),
160
+ "indexes" => {
161
+ "by_type" => @indexes[:by_type].transform_values { |tools| tools.map(&:id) },
162
+ "by_tags" => @indexes[:by_tag].transform_values { |tools| tools.map(&:id) },
163
+ "by_work_unit_type" => @indexes[:by_work_unit_type].transform_values { |tools| tools.map(&:id) }
154
164
  },
155
- dependency_graph: @dependency_graph,
156
- statistics: {
157
- total_tools: @tools.size,
158
- by_type: @tools.group_by(&:type).transform_values(&:size),
159
- total_tags: @indexes[:by_tag].size,
160
- total_work_unit_types: @indexes[:by_work_unit_type].size
165
+ "dependency_graph" => @dependency_graph,
166
+ "errors" => invalid_results.map { |res| {"tool_id" => res.tool_id, "errors" => res.errors, "warnings" => res.warnings} },
167
+ "statistics" => {
168
+ "total_tools" => @tools.size,
169
+ "by_type" => @tools.group_by(&:type).transform_values(&:size),
170
+ "total_tags" => @indexes[:by_tag].size,
171
+ "total_work_unit_types" => @indexes[:by_work_unit_type].size
161
172
  }
162
173
  }
163
174
  end
@@ -223,6 +234,7 @@ module Aidp
223
234
  warnings: result.warnings
224
235
  )
225
236
  end
237
+ invalid_results
226
238
  end
227
239
  end
228
240
  end
@@ -64,7 +64,7 @@ module Aidp
64
64
  Aidp.log_debug("metadata", "Finding by tags", tags: tags, match_all: match_all)
65
65
 
66
66
  tags = Array(tags).map(&:downcase)
67
- indexes = directory["indexes"]["by_tag"]
67
+ indexes = directory["indexes"]["by_tags"] || {}
68
68
 
69
69
  if match_all
70
70
  # Find tools that have ALL specified tags
@@ -17,16 +17,21 @@ module Aidp
17
17
  # Initialize scanner with directory paths
18
18
  #
19
19
  # @param directories [Array<String>] Directories to scan
20
- def initialize(directories = [])
20
+ def initialize(directories = [], strict: false)
21
21
  @directories = Array(directories)
22
+ @strict = strict
23
+ @parse_errors = []
22
24
  end
23
25
 
26
+ attr_reader :parse_errors
27
+
24
28
  # Scan all configured directories
25
29
  #
26
30
  # @return [Array<ToolMetadata>] All discovered tool metadata
27
31
  def scan_all
28
32
  Aidp.log_debug("metadata", "Scanning directories", directories: @directories)
29
33
 
34
+ @parse_errors = []
30
35
  all_tools = []
31
36
  @directories.each do |dir|
32
37
  tools = scan_directory(dir)
@@ -69,6 +74,8 @@ module Aidp
69
74
  file: file_path,
70
75
  error: e.message
71
76
  )
77
+ @parse_errors << {file: file_path, error: e.message}
78
+ raise if @strict
72
79
  end
73
80
 
74
81
  Aidp.log_debug(
@@ -146,19 +146,19 @@ module Aidp
146
146
  # @return [Hash] Metadata as hash (for JSON)
147
147
  def to_h
148
148
  {
149
- type: type,
150
- id: id,
151
- title: title,
152
- summary: summary,
153
- version: version,
154
- applies_to: applies_to,
155
- work_unit_types: work_unit_types,
156
- priority: priority,
157
- capabilities: capabilities,
158
- dependencies: dependencies,
159
- experimental: experimental,
160
- source_path: source_path,
161
- file_hash: file_hash
149
+ "type" => type,
150
+ "id" => id,
151
+ "title" => title,
152
+ "summary" => summary,
153
+ "version" => version,
154
+ "applies_to" => applies_to,
155
+ "work_unit_types" => work_unit_types,
156
+ "priority" => priority,
157
+ "capabilities" => capabilities,
158
+ "dependencies" => dependencies,
159
+ "experimental" => experimental,
160
+ "source_path" => source_path,
161
+ "file_hash" => file_hash
162
162
  }
163
163
  end
164
164
 
@@ -84,6 +84,10 @@ module Aidp
84
84
  warnings << "Tool content is very short (#{tool.content.length} characters)"
85
85
  end
86
86
 
87
+ unless valid_version_format?(tool.version)
88
+ warnings << "Version '#{tool.version}' is not in semver format (MAJOR.MINOR.PATCH)"
89
+ end
90
+
87
91
  ValidationResult.new(
88
92
  tool_id: tool.id,
89
93
  file_path: tool.source_path,
@@ -156,6 +160,12 @@ module Aidp
156
160
  # For now, this is a placeholder for future deprecation warnings
157
161
  end
158
162
 
163
+ def valid_version_format?(version)
164
+ version.to_s.match?(/\A\d+\.\d+\.\d+\z/)
165
+ rescue
166
+ false
167
+ end
168
+
159
169
  # Write validation errors to log file
160
170
  #
161
171
  # @param results [Array<ValidationResult>] Validation results
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aidp
4
+ # Namespace marker for metadata components
5
+ module Metadata
6
+ end
7
+ end
8
+
9
+ # Load key metadata components
10
+ require_relative "metadata/parser"
11
+ require_relative "metadata/tool_metadata"
12
+ require_relative "metadata/scanner"
13
+ require_relative "metadata/validator"
14
+ require_relative "metadata/compiler"
15
+ require_relative "metadata/cache"
16
+ require_relative "metadata/query"
@@ -4,7 +4,7 @@ require "shellwords"
4
4
 
5
5
  module Aidp
6
6
  # Manages worktrees specifically for Pull Request branches
7
- class PrWorktreeManager
7
+ class PRWorktreeManager
8
8
  def initialize(base_repo_path: nil, project_dir: nil, worktree_registry_path: nil)
9
9
  @base_repo_path = base_repo_path || project_dir || Dir.pwd
10
10
  @project_dir = project_dir
@@ -258,7 +258,7 @@ module Aidp
258
258
 
259
259
  # Advanced change detection patterns
260
260
  file_patterns = [
261
- /(?:modify|update|add|delete)\s+file:\s*([^\n]+)/i,
261
+ /(modify|update|add|delete)\s+file:\s*([^\n]+)/i,
262
262
  /\[(\w+)\]\s*([^\n]+)/, # GitHub-style change indicators
263
263
  /(?:Action:\s*(\w+))\s*File:\s*([^\n]+)/i
264
264
  ]
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "tty-prompt"
4
- require_relative "harness/provider_factory"
5
4
 
6
5
  module Aidp
7
6
  class ProviderManager
@@ -20,12 +19,7 @@ module Aidp
20
19
 
21
20
  # Get harness factory instance
22
21
  def get_harness_factory
23
- @harness_factory ||= begin
24
- require_relative "harness/config_manager"
25
- Aidp::Harness::ProviderFactory.new
26
- rescue LoadError
27
- nil
28
- end
22
+ @harness_factory ||= Aidp::Harness::ProviderFactory.new
29
23
  end
30
24
 
31
25
  # Create provider using harness configuration