aidp 0.28.0 → 0.30.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.
@@ -56,7 +56,7 @@ module Aidp
56
56
  "Kilocode"
57
57
  end
58
58
 
59
- def send_message(prompt:, session: nil)
59
+ def send_message(prompt:, session: nil, options: {})
60
60
  raise "kilocode not available" unless self.class.available?
61
61
 
62
62
  # Smart timeout calculation
@@ -56,7 +56,7 @@ module Aidp
56
56
  "OpenCode"
57
57
  end
58
58
 
59
- def send_message(prompt:, session: nil)
59
+ def send_message(prompt:, session: nil, options: {})
60
60
  raise "opencode not available" unless self.class.available?
61
61
 
62
62
  # Smart timeout calculation
@@ -59,9 +59,6 @@ module Aidp
59
59
  configure_modes
60
60
  configure_devcontainer
61
61
 
62
- # Finalize any background model discovery
63
- finalize_background_discovery
64
-
65
62
  yaml_content = generate_yaml
66
63
  display_preview(yaml_content)
67
64
  display_diff(yaml_content) if @existing_config.any?
@@ -355,110 +352,14 @@ module Aidp
355
352
  end
356
353
  end
357
354
 
358
- # Trigger background model discovery for a provider
359
- # Runs asynchronously and caches results without blocking the wizard
360
- def trigger_background_discovery(provider_name)
361
- return unless provider_available_for_discovery?(provider_name)
362
-
363
- # Store reference to display notification later
364
- @discovery_threads ||= []
365
-
366
- thread = Thread.new do
367
- discover_and_cache_models(provider_name)
368
- rescue => e
369
- Aidp.log_debug("setup_wizard", "background discovery failed",
370
- provider: provider_name, error: e.message)
371
- end
372
-
373
- @discovery_threads << {thread: thread, provider: provider_name}
374
- end
375
-
376
- # Check if provider CLI is available for discovery
377
- def provider_available_for_discovery?(provider_name)
378
- provider_class = get_provider_class(provider_name)
379
- return false unless provider_class
380
-
381
- provider_class.respond_to?(:available?) && provider_class.available?
382
- rescue => e
383
- Aidp.log_debug("setup_wizard", "provider availability check failed",
384
- provider: provider_name, error: e.message)
385
- false
386
- end
387
-
388
- # Perform model discovery and cache results
389
- def discover_and_cache_models(provider_name)
390
- require_relative "../harness/model_discovery_service"
391
-
392
- service = Aidp::Harness::ModelDiscoveryService.new
393
- models = service.discover_models(provider_name, use_cache: false)
394
-
395
- if models.any?
396
- Aidp.log_info("setup_wizard", "discovered models in background",
397
- provider: provider_name, count: models.size)
398
- end
399
-
400
- models
401
- rescue => e
402
- Aidp.log_debug("setup_wizard", "background discovery failed",
403
- provider: provider_name, error: e.message)
404
- []
405
- end
406
-
407
- # Get provider class for discovery
408
- def get_provider_class(provider_name)
409
- class_name = "Aidp::Providers::#{provider_name.capitalize}"
410
- Object.const_get(class_name)
411
- rescue NameError
412
- nil
413
- end
414
-
415
- # Wait for background discovery to complete and show notifications
416
- #
417
- # @param timeout [Numeric] Maximum seconds to wait per thread (default: 5)
418
- def finalize_background_discovery(timeout: 5)
419
- return unless @discovery_threads&.any?
420
-
421
- @discovery_threads.each do |entry|
422
- thread = entry[:thread]
423
- provider = entry[:provider]
424
-
425
- # Wait up to timeout seconds for discovery to complete
426
- thread.join(timeout)
427
-
428
- if thread.alive?
429
- Aidp.log_debug("setup_wizard", "discovery timeout, killing thread",
430
- provider: provider)
431
- # Kill thread to prevent hanging
432
- thread.kill
433
- thread.join(0.1) # Brief wait for cleanup
434
- else
435
- # Discovery completed - show notification
436
- begin
437
- require_relative "../harness/model_cache"
438
- cache = Aidp::Harness::ModelCache.new
439
- cached_models = cache.get_cached_models(provider)
440
-
441
- if cached_models&.any?
442
- prompt.say(" 💾 Discovered #{cached_models.size} model#{"s" unless cached_models.size == 1} for #{provider}")
443
- end
444
- rescue => e
445
- Aidp.log_debug("setup_wizard", "failed to check cached models",
446
- provider: provider, error: e.message)
447
- end
448
- end
449
- end
450
-
451
- @discovery_threads = []
452
- end
453
-
454
355
  def discover_models_for_providers(providers)
455
- require_relative "../harness/model_discovery_service"
356
+ require_relative "../harness/ruby_llm_registry"
456
357
 
457
- service = Aidp::Harness::ModelDiscoveryService.new
358
+ registry = Aidp::Harness::RubyLLMRegistry.new
458
359
  all_models = {}
459
360
 
460
361
  providers.each do |provider|
461
- models = service.discover_models(provider, use_cache: true)
362
+ models = discover_models_from_registry(provider, registry)
462
363
  all_models[provider] = models if models.any?
463
364
  rescue => e
464
365
  Aidp.log_debug("setup_wizard", "discovery failed", provider: provider, error: e.message)
@@ -468,6 +369,30 @@ module Aidp
468
369
  all_models
469
370
  end
470
371
 
372
+ # Discover models for a provider from RubyLLM registry
373
+ #
374
+ # @param provider [String] Provider name (e.g., "anthropic")
375
+ # @param registry [RubyLLMRegistry] Registry instance
376
+ # @return [Array<Hash>] Models with tier classification
377
+ def discover_models_from_registry(provider, registry)
378
+ model_ids = registry.models_for_provider(provider)
379
+
380
+ # Convert to model info hashes with tier classification
381
+ model_ids.map do |model_id|
382
+ info = registry.get_model_info(model_id)
383
+ {
384
+ name: model_id,
385
+ tier: info[:tier],
386
+ context_window: info[:context_window],
387
+ capabilities: info[:capabilities]
388
+ }
389
+ end
390
+ rescue => e
391
+ Aidp.log_error("setup_wizard", "failed to discover models from registry",
392
+ provider: provider, error: e.message)
393
+ []
394
+ end
395
+
471
396
  def display_discovered_models(discovered_models)
472
397
  discovered_models.each do |provider, models|
473
398
  prompt.say("\n✓ Found #{models.size} models for #{provider}:")
@@ -510,13 +435,19 @@ module Aidp
510
435
  def find_model_for_tier(models, target_tier)
511
436
  return nil unless models
512
437
 
513
- models.find { |m| m[:tier] == target_tier }
438
+ # Map pro -> advanced for registry compatibility
439
+ registry_tier = (target_tier == "pro") ? "advanced" : target_tier
440
+
441
+ models.find { |m| m[:tier] == registry_tier }
514
442
  end
515
443
 
516
444
  def find_models_for_tier(models, target_tier)
517
445
  return [] unless models
518
446
 
519
- models.select { |m| m[:tier] == target_tier }
447
+ # Map pro -> advanced for registry compatibility
448
+ registry_tier = (target_tier == "pro") ? "advanced" : target_tier
449
+
450
+ models.select { |m| m[:tier] == registry_tier }
520
451
  end
521
452
 
522
453
  def display_provider_tier_preview(provider_configs)
@@ -1717,9 +1648,6 @@ module Aidp
1717
1648
  # Enhance messaging with display name when available
1718
1649
  display_name = discover_available_providers.invert.fetch(provider_name, provider_name)
1719
1650
  prompt.say(" • #{action_word.capitalize} provider '#{display_name}' (#{provider_name}) with billing type '#{provider_type}' and model family '#{model_family}'")
1720
-
1721
- # Trigger background model discovery for newly added/updated provider
1722
- trigger_background_discovery(provider_name) unless @dry_run
1723
1651
  end
1724
1652
 
1725
1653
  def edit_or_remove_provider(provider_name, primary_provider, fallbacks)
data/lib/aidp/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Aidp
4
- VERSION = "0.28.0"
4
+ VERSION = "0.30.0"
5
5
  end
data/lib/aidp.rb CHANGED
@@ -49,6 +49,15 @@ require_relative "aidp/analyze/seams"
49
49
  require_relative "aidp/analyze/tree_sitter_scan"
50
50
  require_relative "aidp/analyze/kb_inspector"
51
51
 
52
+ # Metadata system
53
+ require_relative "aidp/metadata/tool_metadata"
54
+ require_relative "aidp/metadata/validator"
55
+ require_relative "aidp/metadata/parser"
56
+ require_relative "aidp/metadata/scanner"
57
+ require_relative "aidp/metadata/compiler"
58
+ require_relative "aidp/metadata/query"
59
+ require_relative "aidp/metadata/cache"
60
+
52
61
  # Workflows
53
62
  require_relative "aidp/workflows/definitions"
54
63
  require_relative "aidp/workflows/selector"
@@ -83,6 +92,8 @@ require_relative "aidp/harness/config_schema"
83
92
  require_relative "aidp/harness/config_validator"
84
93
  require_relative "aidp/harness/config_loader"
85
94
  require_relative "aidp/harness/config_manager"
95
+ require_relative "aidp/harness/ruby_llm_registry"
96
+ require_relative "aidp/harness/model_registry"
86
97
  require_relative "aidp/harness/condition_detector"
87
98
  require_relative "aidp/harness/user_interface"
88
99
  require_relative "aidp/harness/simple_user_interface"
@@ -23,7 +23,10 @@ You are a senior engineer writing **implementation guidance** for domain agents.
23
23
 
24
24
  ## Output
25
25
 
26
- - `docs/ImplementationGuide.md` with examples and a pattern-to-use-case matrix.
26
+ - `docs/<FEATURE_NAME>_IMPLEMENTATION.md` with examples and a pattern-to-use-case matrix.
27
+ - Use a descriptive name based on the feature/issue (e.g., `METADATA_TOOL_DISCOVERY.md`, `WORKTREE_PR_CHANGE_REQUESTS.md`)
28
+ - Do NOT use generic names like `ImplementationGuide.md`, `IMPLEMENTATION_SUMMARY.md`, or `SESSION_SUMMARY.md`
29
+ - Do NOT create files at the project root
27
30
 
28
31
  ## Regeneration Policy
29
32
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: aidp
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.28.0
4
+ version: 0.30.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bart Agapinan
@@ -219,6 +219,20 @@ dependencies:
219
219
  - - "~>"
220
220
  - !ruby/object:Gem::Version
221
221
  version: '0.10'
222
+ - !ruby/object:Gem::Dependency
223
+ name: ruby_llm
224
+ requirement: !ruby/object:Gem::Requirement
225
+ requirements:
226
+ - - "~>"
227
+ - !ruby/object:Gem::Version
228
+ version: '1.9'
229
+ type: :runtime
230
+ prerelease: false
231
+ version_requirements: !ruby/object:Gem::Requirement
232
+ requirements:
233
+ - - "~>"
234
+ - !ruby/object:Gem::Version
235
+ version: '1.9'
222
236
  description: The AI-Dev-Pipeline (AIDP) CLI provides a powerful, markdown-driven workflow
223
237
  for software development. It supports in-depth project analysis to understand existing
224
238
  codebases and an execution mode to systematically implement new features.
@@ -269,6 +283,7 @@ files:
269
283
  - lib/aidp/cli/models_command.rb
270
284
  - lib/aidp/cli/providers_command.rb
271
285
  - lib/aidp/cli/terminal_io.rb
286
+ - lib/aidp/cli/tools_command.rb
272
287
  - lib/aidp/concurrency.rb
273
288
  - lib/aidp/concurrency/backoff.rb
274
289
  - lib/aidp/concurrency/exec.rb
@@ -309,12 +324,12 @@ files:
309
324
  - lib/aidp/harness/config_schema.rb
310
325
  - lib/aidp/harness/config_validator.rb
311
326
  - lib/aidp/harness/configuration.rb
327
+ - lib/aidp/harness/deprecation_cache.rb
312
328
  - lib/aidp/harness/enhanced_runner.rb
313
329
  - lib/aidp/harness/error_handler.rb
314
330
  - lib/aidp/harness/filter_strategy.rb
315
331
  - lib/aidp/harness/generic_filter_strategy.rb
316
332
  - lib/aidp/harness/model_cache.rb
317
- - lib/aidp/harness/model_discovery_service.rb
318
333
  - lib/aidp/harness/model_registry.rb
319
334
  - lib/aidp/harness/output_filter.rb
320
335
  - lib/aidp/harness/provider_config.rb
@@ -324,6 +339,7 @@ files:
324
339
  - lib/aidp/harness/provider_metrics.rb
325
340
  - lib/aidp/harness/provider_type_checker.rb
326
341
  - lib/aidp/harness/rspec_filter_strategy.rb
342
+ - lib/aidp/harness/ruby_llm_registry.rb
327
343
  - lib/aidp/harness/runner.rb
328
344
  - lib/aidp/harness/simple_user_interface.rb
329
345
  - lib/aidp/harness/state/errors.rb
@@ -365,6 +381,13 @@ files:
365
381
  - lib/aidp/jobs/background_runner.rb
366
382
  - lib/aidp/logger.rb
367
383
  - lib/aidp/message_display.rb
384
+ - lib/aidp/metadata/cache.rb
385
+ - lib/aidp/metadata/compiler.rb
386
+ - lib/aidp/metadata/parser.rb
387
+ - lib/aidp/metadata/query.rb
388
+ - lib/aidp/metadata/scanner.rb
389
+ - lib/aidp/metadata/tool_metadata.rb
390
+ - lib/aidp/metadata/validator.rb
368
391
  - lib/aidp/planning/analyzers/feedback_analyzer.rb
369
392
  - lib/aidp/planning/builders/agile_plan_builder.rb
370
393
  - lib/aidp/planning/builders/project_plan_builder.rb
@@ -1,259 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "model_cache"
4
- require_relative "model_registry"
5
-
6
- module Aidp
7
- module Harness
8
- # Service for discovering available models from providers
9
- #
10
- # Orchestrates model discovery across multiple providers:
11
- # 1. Checks cache first (with TTL)
12
- # 2. Falls back to dynamic discovery via provider.discover_models
13
- # 3. Merges with static registry for comprehensive results
14
- # 4. Caches results for future use
15
- #
16
- # Usage:
17
- # service = ModelDiscoveryService.new
18
- # models = service.discover_models("anthropic")
19
- # all_models = service.discover_all_models
20
- class ModelDiscoveryService
21
- attr_reader :cache, :registry
22
-
23
- def initialize(cache: nil, registry: nil)
24
- @cache = cache || ModelCache.new
25
- @registry = registry || ModelRegistry.new
26
- @provider_classes = discover_provider_classes
27
- Aidp.log_debug("model_discovery_service", "initialized",
28
- providers: @provider_classes.keys)
29
- end
30
-
31
- # Discover models for a specific provider
32
- #
33
- # @param provider [String] Provider name (e.g., "anthropic", "cursor")
34
- # @param use_cache [Boolean] Whether to use cached results (default: true)
35
- # @return [Array<Hash>] Discovered models
36
- def discover_models(provider, use_cache: true)
37
- Aidp.log_info("model_discovery_service", "discovering models",
38
- provider: provider, use_cache: use_cache)
39
-
40
- # Check cache first
41
- if use_cache
42
- cached = @cache.get_cached_models(provider)
43
- if cached
44
- Aidp.log_debug("model_discovery_service", "using cached models",
45
- provider: provider, count: cached.size)
46
- return cached
47
- end
48
- end
49
-
50
- # Perform discovery
51
- models = perform_discovery(provider)
52
-
53
- # Cache the results
54
- @cache.cache_models(provider, models) if models.any?
55
-
56
- models
57
- rescue => e
58
- Aidp.log_error("model_discovery_service", "discovery failed",
59
- provider: provider, error: e.message, backtrace: e.backtrace.first(3))
60
- []
61
- end
62
-
63
- # Discover models from all available providers
64
- #
65
- # @param use_cache [Boolean] Whether to use cached results
66
- # @return [Hash] Hash of provider => models array
67
- def discover_all_models(use_cache: true)
68
- results = {}
69
-
70
- @provider_classes.each_key do |provider|
71
- models = discover_models(provider, use_cache: use_cache)
72
- results[provider] = models if models.any?
73
- end
74
-
75
- Aidp.log_info("model_discovery_service", "discovered all models",
76
- providers: results.keys, total_models: results.values.flatten.size)
77
- results
78
- end
79
-
80
- # Discover models concurrently from multiple providers
81
- #
82
- # @param providers [Array<String>] List of provider names
83
- # @param use_cache [Boolean] Whether to use cached results
84
- # @return [Hash] Hash of provider => models array
85
- def discover_concurrent(providers, use_cache: true)
86
- require "concurrent"
87
-
88
- results = {}
89
- mutex = Mutex.new
90
-
91
- # Create a thread pool
92
- pool = Concurrent::FixedThreadPool.new(providers.size)
93
-
94
- # Submit discovery tasks
95
- futures = providers.map do |provider|
96
- Concurrent::Future.execute(executor: pool) do
97
- models = discover_models(provider, use_cache: use_cache)
98
- mutex.synchronize { results[provider] = models }
99
- end
100
- end
101
-
102
- # Wait for all to complete
103
- futures.each(&:wait)
104
-
105
- pool.shutdown
106
- pool.wait_for_termination(30)
107
-
108
- Aidp.log_info("model_discovery_service", "concurrent discovery complete",
109
- providers: results.keys, total_models: results.values.flatten.size)
110
- results
111
- rescue LoadError => e
112
- # Fallback to sequential if concurrent gem not available
113
- Aidp.log_warn("model_discovery_service", "concurrent gem not available, using sequential",
114
- error: e.message)
115
- providers.each_with_object({}) do |provider, hash|
116
- hash[provider] = discover_models(provider, use_cache: use_cache)
117
- end
118
- rescue => e
119
- Aidp.log_error("model_discovery_service", "concurrent discovery failed",
120
- error: e.message)
121
- {}
122
- end
123
-
124
- # Get all available models (discovery + static registry)
125
- #
126
- # Combines dynamically discovered models with static registry
127
- #
128
- # @param use_cache [Boolean] Whether to use cached results
129
- # @return [Hash] Hash with :discovered and :registry keys
130
- def all_available_models(use_cache: true)
131
- discovered = discover_all_models(use_cache: use_cache)
132
- registry_families = @registry.all_families
133
-
134
- {
135
- discovered: discovered,
136
- registry: registry_families.map { |family| @registry.get_model_info(family) }.compact
137
- }
138
- end
139
-
140
- # Find which providers support a given model family
141
- #
142
- # @param family_name [String] Model family name
143
- # @return [Array<String>] List of provider names
144
- def providers_supporting(family_name)
145
- providers = []
146
-
147
- @provider_classes.each do |provider_name, class_name|
148
- provider_class = constantize_provider(class_name)
149
- next unless provider_class
150
-
151
- if provider_class.respond_to?(:supports_model_family?)
152
- providers << provider_name if provider_class.supports_model_family?(family_name)
153
- end
154
- end
155
-
156
- providers
157
- end
158
-
159
- # Refresh cache for all providers
160
- def refresh_all_caches
161
- @cache.invalidate_all
162
- discover_all_models(use_cache: false)
163
- end
164
-
165
- # Refresh cache for specific provider
166
- #
167
- # @param provider [String] Provider name
168
- def refresh_cache(provider)
169
- @cache.invalidate(provider)
170
- discover_models(provider, use_cache: false)
171
- end
172
-
173
- private
174
-
175
- def perform_discovery(provider)
176
- provider_class = get_provider_class(provider)
177
- unless provider_class
178
- Aidp.log_warn("model_discovery_service", "unknown provider",
179
- provider: provider)
180
- return []
181
- end
182
-
183
- unless provider_class.respond_to?(:available?) && provider_class.available?
184
- Aidp.log_debug("model_discovery_service", "provider not available",
185
- provider: provider)
186
- return []
187
- end
188
-
189
- unless provider_class.respond_to?(:discover_models)
190
- Aidp.log_warn("model_discovery_service", "provider missing discover_models",
191
- provider: provider)
192
- return []
193
- end
194
-
195
- models = provider_class.discover_models
196
- Aidp.log_info("model_discovery_service", "discovered models",
197
- provider: provider, count: models.size)
198
- models
199
- end
200
-
201
- def get_provider_class(provider)
202
- class_name = @provider_classes[provider]
203
- return nil unless class_name
204
-
205
- constantize_provider(class_name)
206
- end
207
-
208
- def constantize_provider(class_name)
209
- # Safely constantize the provider class
210
- parts = class_name.split("::")
211
- parts.reduce(Object) { |mod, name| mod.const_get(name) }
212
- rescue NameError => e
213
- Aidp.log_debug("model_discovery_service", "provider class not found",
214
- class: class_name, error: e.message)
215
- nil
216
- end
217
-
218
- # Dynamically discover all provider classes from the providers directory
219
- #
220
- # @return [Hash] Hash of provider_name => class_name
221
- def discover_provider_classes
222
- providers_dir = File.join(__dir__, "../providers")
223
- provider_files = Dir.glob("*.rb", base: providers_dir)
224
-
225
- # Exclude base classes and utility files
226
- excluded_files = ["base.rb", "adapter.rb", "error_taxonomy.rb", "capability_registry.rb"]
227
- provider_files -= excluded_files
228
-
229
- providers = {}
230
-
231
- provider_files.each do |file|
232
- provider_name = File.basename(file, ".rb")
233
- # Convert to class name (e.g., "anthropic" -> "Anthropic", "github_copilot" -> "GithubCopilot")
234
- class_name = provider_name.split("_").map(&:capitalize).join
235
- full_class_name = "Aidp::Providers::#{class_name}"
236
-
237
- # Try to load and verify the provider class exists
238
- begin
239
- require_relative "../providers/#{provider_name}"
240
- provider_class = constantize_provider(full_class_name)
241
- if provider_class&.respond_to?(:discover_models)
242
- providers[provider_name] = full_class_name
243
- end
244
- rescue => e
245
- # Skip providers that can't be loaded or don't implement discover_models
246
- if ENV["DEBUG"]
247
- Aidp.log_debug("model_discovery_service", "skipping provider",
248
- provider: provider_name, reason: e.message)
249
- end
250
- end
251
- end
252
-
253
- Aidp.log_debug("model_discovery_service", "discovered provider classes",
254
- count: providers.size, providers: providers.keys)
255
- providers
256
- end
257
- end
258
- end
259
- end