aidp 0.28.0 → 0.29.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 +4 -4
- data/lib/aidp/cli/models_command.rb +75 -117
- data/lib/aidp/cli/tools_command.rb +333 -0
- data/lib/aidp/cli.rb +8 -1
- data/lib/aidp/config.rb +9 -0
- data/lib/aidp/execute/work_loop_runner.rb +6 -3
- data/lib/aidp/harness/capability_registry.rb +4 -4
- data/lib/aidp/harness/provider_manager.rb +5 -3
- data/lib/aidp/harness/ruby_llm_registry.rb +239 -0
- data/lib/aidp/metadata/cache.rb +201 -0
- data/lib/aidp/metadata/compiler.rb +229 -0
- data/lib/aidp/metadata/parser.rb +204 -0
- data/lib/aidp/metadata/query.rb +237 -0
- data/lib/aidp/metadata/scanner.rb +191 -0
- data/lib/aidp/metadata/tool_metadata.rb +245 -0
- data/lib/aidp/metadata/validator.rb +187 -0
- data/lib/aidp/providers/aider.rb +1 -1
- data/lib/aidp/providers/anthropic.rb +6 -4
- data/lib/aidp/providers/base.rb +105 -35
- data/lib/aidp/providers/codex.rb +1 -1
- data/lib/aidp/providers/cursor.rb +1 -1
- data/lib/aidp/providers/gemini.rb +1 -1
- data/lib/aidp/providers/github_copilot.rb +1 -1
- data/lib/aidp/providers/kilocode.rb +1 -1
- data/lib/aidp/providers/opencode.rb +1 -1
- data/lib/aidp/setup/wizard.rb +35 -107
- data/lib/aidp/version.rb +1 -1
- data/lib/aidp.rb +11 -0
- data/templates/implementation/implement_features.md +4 -1
- metadata +24 -2
- data/lib/aidp/harness/model_discovery_service.rb +0 -259
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 00cbc8656c3ce1550f5246837062879c70a0206fa62c00a7bef6cd20979b9be5
|
|
4
|
+
data.tar.gz: 353616de5a90d4cf722673040d6e7fba09d60ff63618a3c135688202694e3075
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 65109acb0af57d4fefccbefa626f928cec5faaa3eedb5dfe4c0141521e03d95c8f7ca2dac8455baebdf6c14d0cbcd6cd81557a0bf4b2f0cdadb62547f60b414d
|
|
7
|
+
data.tar.gz: ea289ac3406776ccc1c602795fe6fa0a54c09fe8b3729b7e0f3b0151ba2782613cc2fee68b1111f9f6b81c78efeb7d31e77ab233ea49fba48e6d5585505e60f8
|
|
@@ -4,28 +4,28 @@ require "tty-table"
|
|
|
4
4
|
require "tty-prompt"
|
|
5
5
|
require "tty-spinner"
|
|
6
6
|
require_relative "../harness/model_registry"
|
|
7
|
-
require_relative "../harness/
|
|
7
|
+
require_relative "../harness/ruby_llm_registry"
|
|
8
8
|
|
|
9
9
|
module Aidp
|
|
10
10
|
class CLI
|
|
11
11
|
# Command handler for `aidp models` subcommand group
|
|
12
12
|
#
|
|
13
|
-
# Provides commands for viewing
|
|
13
|
+
# Provides commands for viewing AI models from the RubyLLM registry:
|
|
14
14
|
# - list: Show all available models with tier information
|
|
15
|
-
# - discover: Discover models from
|
|
16
|
-
# -
|
|
15
|
+
# - discover: Discover models from RubyLLM registry for a provider
|
|
16
|
+
# - validate: Validate model configuration
|
|
17
17
|
#
|
|
18
18
|
# Usage:
|
|
19
19
|
# aidp models list [--provider=<name>] [--tier=<tier>]
|
|
20
20
|
# aidp models discover [--provider=<name>]
|
|
21
|
-
# aidp models
|
|
21
|
+
# aidp models validate
|
|
22
22
|
class ModelsCommand
|
|
23
23
|
include Aidp::MessageDisplay
|
|
24
24
|
|
|
25
|
-
def initialize(prompt: TTY::Prompt.new, registry: nil,
|
|
25
|
+
def initialize(prompt: TTY::Prompt.new, registry: nil, ruby_llm_registry: nil)
|
|
26
26
|
@prompt = prompt
|
|
27
27
|
@registry = registry
|
|
28
|
-
@
|
|
28
|
+
@ruby_llm_registry = ruby_llm_registry
|
|
29
29
|
end
|
|
30
30
|
|
|
31
31
|
# Main entry point for models subcommand
|
|
@@ -39,9 +39,6 @@ module Aidp
|
|
|
39
39
|
when "discover"
|
|
40
40
|
args.shift
|
|
41
41
|
run_discover_command(args)
|
|
42
|
-
when "refresh"
|
|
43
|
-
args.shift
|
|
44
|
-
run_refresh_command(args)
|
|
45
42
|
when "validate"
|
|
46
43
|
args.shift
|
|
47
44
|
run_validate_command(args)
|
|
@@ -58,16 +55,15 @@ module Aidp
|
|
|
58
55
|
@registry ||= Aidp::Harness::ModelRegistry.new
|
|
59
56
|
end
|
|
60
57
|
|
|
61
|
-
def
|
|
62
|
-
@
|
|
58
|
+
def ruby_llm_registry
|
|
59
|
+
@ruby_llm_registry ||= Aidp::Harness::RubyLLMRegistry.new
|
|
63
60
|
end
|
|
64
61
|
|
|
65
62
|
def display_help
|
|
66
63
|
display_message("\nUsage: aidp models <subcommand> [options]", type: :info)
|
|
67
64
|
display_message("\nSubcommands:", type: :info)
|
|
68
65
|
display_message(" list List all available models with tier information", type: :info)
|
|
69
|
-
display_message(" discover Discover models from
|
|
70
|
-
display_message(" refresh Refresh the model discovery cache", type: :info)
|
|
66
|
+
display_message(" discover Discover models from RubyLLM registry for a provider", type: :info)
|
|
71
67
|
display_message(" validate Validate model configuration for all tiers", type: :info)
|
|
72
68
|
display_message("\nOptions:", type: :info)
|
|
73
69
|
display_message(" --provider=<name> Filter/target specific provider", type: :info)
|
|
@@ -76,9 +72,7 @@ module Aidp
|
|
|
76
72
|
display_message(" aidp models list", type: :info)
|
|
77
73
|
display_message(" aidp models list --tier=mini", type: :info)
|
|
78
74
|
display_message(" aidp models list --provider=anthropic", type: :info)
|
|
79
|
-
display_message(" aidp models discover", type: :info)
|
|
80
75
|
display_message(" aidp models discover --provider=anthropic", type: :info)
|
|
81
|
-
display_message(" aidp models refresh", type: :info)
|
|
82
76
|
display_message(" aidp models validate", type: :info)
|
|
83
77
|
end
|
|
84
78
|
|
|
@@ -86,42 +80,35 @@ module Aidp
|
|
|
86
80
|
options = parse_list_options(args)
|
|
87
81
|
|
|
88
82
|
begin
|
|
89
|
-
# Get
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
info && info["tier"] == options[:tier]
|
|
97
|
-
}
|
|
83
|
+
# Get models from RubyLLM registry
|
|
84
|
+
all_model_ids = if options[:provider]
|
|
85
|
+
ruby_llm_registry.models_for_provider(options[:provider])
|
|
86
|
+
else
|
|
87
|
+
# Get all models by iterating known providers
|
|
88
|
+
known_providers = %w[anthropic openai google azure bedrock openrouter]
|
|
89
|
+
known_providers.flat_map { |p| ruby_llm_registry.models_for_provider(p) }.uniq
|
|
98
90
|
end
|
|
99
91
|
|
|
100
92
|
# Build table rows
|
|
101
93
|
rows = []
|
|
102
|
-
|
|
103
|
-
info =
|
|
94
|
+
all_model_ids.each do |model_id|
|
|
95
|
+
info = ruby_llm_registry.get_model_info(model_id)
|
|
104
96
|
next unless info
|
|
105
97
|
|
|
106
|
-
#
|
|
107
|
-
|
|
98
|
+
# Map advanced -> pro for display consistency
|
|
99
|
+
display_tier = (info[:tier] == "advanced") ? "pro" : info[:tier]
|
|
108
100
|
|
|
109
|
-
# Apply
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
if providers.empty?
|
|
114
|
-
# No provider support - show as registry-only
|
|
115
|
-
rows << build_table_row(nil, family, info, "registry")
|
|
116
|
-
else
|
|
117
|
-
providers.each do |provider_name|
|
|
118
|
-
rows << build_table_row(provider_name, family, info, "registry")
|
|
119
|
-
end
|
|
101
|
+
# Apply tier filter if specified (handle pro/advanced mapping)
|
|
102
|
+
if options[:tier]
|
|
103
|
+
filter_tier = (options[:tier] == "pro") ? "advanced" : options[:tier]
|
|
104
|
+
next unless info[:tier] == filter_tier
|
|
120
105
|
end
|
|
106
|
+
|
|
107
|
+
rows << build_table_row(info[:provider], model_id, info, display_tier)
|
|
121
108
|
end
|
|
122
109
|
|
|
123
110
|
# Sort rows by tier, then provider, then model name
|
|
124
|
-
tier_order = {"mini" => 0, "standard" => 1, "advanced" => 2}
|
|
111
|
+
tier_order = {"mini" => 0, "standard" => 1, "pro" => 2, "advanced" => 2}
|
|
125
112
|
rows.sort_by! { |r| [tier_order[r[2]] || 3, r[0] || "", r[1]] }
|
|
126
113
|
|
|
127
114
|
# Display table
|
|
@@ -156,6 +143,7 @@ module Aidp
|
|
|
156
143
|
|
|
157
144
|
def parse_list_options(args)
|
|
158
145
|
options = {}
|
|
146
|
+
valid_tiers = %w[mini standard pro advanced]
|
|
159
147
|
|
|
160
148
|
args.each do |arg|
|
|
161
149
|
case arg
|
|
@@ -163,8 +151,8 @@ module Aidp
|
|
|
163
151
|
options[:provider] = Regexp.last_match(1)
|
|
164
152
|
when /^--tier=(.+)$/
|
|
165
153
|
tier = Regexp.last_match(1)
|
|
166
|
-
unless
|
|
167
|
-
display_message("Invalid tier: #{tier}. Valid tiers: #{
|
|
154
|
+
unless valid_tiers.include?(tier)
|
|
155
|
+
display_message("Invalid tier: #{tier}. Valid tiers: #{valid_tiers.join(", ")}", type: :error)
|
|
168
156
|
exit 1
|
|
169
157
|
end
|
|
170
158
|
options[:tier] = tier
|
|
@@ -177,18 +165,17 @@ module Aidp
|
|
|
177
165
|
options
|
|
178
166
|
end
|
|
179
167
|
|
|
180
|
-
def build_table_row(provider_name,
|
|
181
|
-
capabilities = (info[
|
|
182
|
-
context = format_context_window(info[
|
|
183
|
-
speed = info["speed"] || "unknown"
|
|
168
|
+
def build_table_row(provider_name, model_id, info, display_tier)
|
|
169
|
+
capabilities = (info[:capabilities] || []).join(",")
|
|
170
|
+
context = format_context_window(info[:context_window])
|
|
184
171
|
|
|
185
172
|
[
|
|
186
173
|
provider_name || "-",
|
|
187
|
-
|
|
188
|
-
|
|
174
|
+
model_id,
|
|
175
|
+
display_tier || "unknown",
|
|
189
176
|
capabilities.empty? ? "-" : capabilities,
|
|
190
177
|
context,
|
|
191
|
-
|
|
178
|
+
"-" # Speed not available in registry
|
|
192
179
|
]
|
|
193
180
|
end
|
|
194
181
|
|
|
@@ -213,8 +200,8 @@ module Aidp
|
|
|
213
200
|
|
|
214
201
|
def build_footer(count)
|
|
215
202
|
tips = [
|
|
216
|
-
"💡 Showing #{count} model#{"s" unless count == 1} from
|
|
217
|
-
"💡
|
|
203
|
+
"💡 Showing #{count} model#{"s" unless count == 1} from RubyLLM registry",
|
|
204
|
+
"💡 Registry updated regularly via ruby_llm gem updates"
|
|
218
205
|
]
|
|
219
206
|
tips.join("\n")
|
|
220
207
|
end
|
|
@@ -249,81 +236,56 @@ module Aidp
|
|
|
249
236
|
def run_discover_command(args)
|
|
250
237
|
options = parse_discover_options(args)
|
|
251
238
|
|
|
252
|
-
|
|
253
|
-
display_message("\n
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
# Discover models
|
|
259
|
-
results = if options[:provider]
|
|
260
|
-
{options[:provider] => discovery_service.discover_models(options[:provider], use_cache: false)}
|
|
261
|
-
else
|
|
262
|
-
discovery_service.discover_all_models(use_cache: false)
|
|
263
|
-
end
|
|
239
|
+
unless options[:provider]
|
|
240
|
+
display_message("\n⚠️ Please specify a provider with --provider=<name>", type: :warning)
|
|
241
|
+
display_message("Example: aidp models discover --provider=anthropic\n", type: :info)
|
|
242
|
+
return 1
|
|
243
|
+
end
|
|
264
244
|
|
|
265
|
-
|
|
245
|
+
begin
|
|
246
|
+
display_message("\n🔍 Discovering models for #{options[:provider]} from RubyLLM registry...\n", type: :highlight)
|
|
266
247
|
|
|
267
|
-
#
|
|
268
|
-
|
|
269
|
-
results.each do |provider, models|
|
|
270
|
-
next if models.empty?
|
|
271
|
-
|
|
272
|
-
total_models += models.size
|
|
273
|
-
display_message("\n✓ Found #{models.size} models for #{provider}:", type: :success)
|
|
274
|
-
|
|
275
|
-
# Group by tier
|
|
276
|
-
by_tier = models.group_by { |m| m[:tier] }
|
|
277
|
-
%w[mini standard advanced].each do |tier|
|
|
278
|
-
tier_models = by_tier[tier] || []
|
|
279
|
-
next if tier_models.empty?
|
|
280
|
-
|
|
281
|
-
display_message(" #{tier.capitalize} tier:", type: :info)
|
|
282
|
-
tier_models.each do |model|
|
|
283
|
-
display_message(" - #{model[:name]}", type: :info)
|
|
284
|
-
end
|
|
285
|
-
end
|
|
286
|
-
end
|
|
248
|
+
# Get models from registry
|
|
249
|
+
model_ids = ruby_llm_registry.models_for_provider(options[:provider])
|
|
287
250
|
|
|
288
|
-
if
|
|
289
|
-
display_message("\n⚠️ No models
|
|
251
|
+
if model_ids.empty?
|
|
252
|
+
display_message("\n⚠️ No models found for provider '#{options[:provider]}'.", type: :warning)
|
|
253
|
+
display_message("Provider may not be supported or name may be incorrect.\n", type: :info)
|
|
290
254
|
return 1
|
|
291
255
|
end
|
|
292
256
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
Aidp.log_error("models_command", "discovery error", error: e.message, backtrace: e.backtrace.first(5))
|
|
299
|
-
1
|
|
300
|
-
end
|
|
301
|
-
end
|
|
257
|
+
# Group by tier
|
|
258
|
+
models_by_tier = Hash.new { |h, k| h[k] = [] }
|
|
259
|
+
model_ids.each do |model_id|
|
|
260
|
+
info = ruby_llm_registry.get_model_info(model_id)
|
|
261
|
+
next unless info
|
|
302
262
|
|
|
303
|
-
|
|
304
|
-
|
|
263
|
+
# Map advanced -> pro for display
|
|
264
|
+
tier = (info[:tier] == "advanced") ? "pro" : info[:tier]
|
|
265
|
+
models_by_tier[tier] << model_id
|
|
266
|
+
end
|
|
305
267
|
|
|
306
|
-
|
|
307
|
-
display_message("\n
|
|
268
|
+
# Display results
|
|
269
|
+
display_message("\n✓ Found #{model_ids.size} models for #{options[:provider]}:", type: :success)
|
|
308
270
|
|
|
309
|
-
|
|
310
|
-
|
|
271
|
+
%w[mini standard pro].each do |tier|
|
|
272
|
+
tier_models = models_by_tier[tier]
|
|
273
|
+
next if tier_models.empty?
|
|
311
274
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
display_message("\n✅ Refreshed cache for all providers", type: :success)
|
|
275
|
+
display_message("\n #{tier.capitalize} tier (#{tier_models.size} model#{"s" unless tier_models.size == 1}):", type: :info)
|
|
276
|
+
tier_models.first(5).each do |model|
|
|
277
|
+
display_message(" - #{model}", type: :info)
|
|
278
|
+
end
|
|
279
|
+
if tier_models.size > 5
|
|
280
|
+
display_message(" ... and #{tier_models.size - 5} more", type: :info)
|
|
281
|
+
end
|
|
320
282
|
end
|
|
321
283
|
|
|
322
|
-
display_message("💡
|
|
284
|
+
display_message("\n💡 Use 'aidp models list --provider=#{options[:provider]}' to see full details\n", type: :info)
|
|
323
285
|
0
|
|
324
286
|
rescue => e
|
|
325
|
-
display_message("Error
|
|
326
|
-
Aidp.log_error("models_command", "
|
|
287
|
+
display_message("Error discovering models: #{e.message}", type: :error)
|
|
288
|
+
Aidp.log_error("models_command", "discovery error", error: e.message, backtrace: e.backtrace.first(5))
|
|
327
289
|
1
|
|
328
290
|
end
|
|
329
291
|
end
|
|
@@ -344,10 +306,6 @@ module Aidp
|
|
|
344
306
|
options
|
|
345
307
|
end
|
|
346
308
|
|
|
347
|
-
def parse_refresh_options(args)
|
|
348
|
-
parse_discover_options(args)
|
|
349
|
-
end
|
|
350
|
-
|
|
351
309
|
def run_validate_command(args)
|
|
352
310
|
parse_validate_options(args)
|
|
353
311
|
|
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "tty-prompt"
|
|
4
|
+
require "tty-table"
|
|
5
|
+
require_relative "../config"
|
|
6
|
+
require_relative "../metadata/cache"
|
|
7
|
+
require_relative "../metadata/query"
|
|
8
|
+
require_relative "../metadata/validator"
|
|
9
|
+
|
|
10
|
+
module Aidp
|
|
11
|
+
class CLI
|
|
12
|
+
# CLI commands for managing tool metadata
|
|
13
|
+
#
|
|
14
|
+
# Provides commands for:
|
|
15
|
+
# - aidp tools lint - Validate all metadata
|
|
16
|
+
# - aidp tools info <id> - Display tool details
|
|
17
|
+
# - aidp tools reload - Force cache regeneration
|
|
18
|
+
# - aidp tools list - List all tools
|
|
19
|
+
class ToolsCommand
|
|
20
|
+
# Initialize tools command
|
|
21
|
+
#
|
|
22
|
+
# @param project_dir [String] Project directory path
|
|
23
|
+
# @param prompt [TTY::Prompt] TTY prompt instance
|
|
24
|
+
def initialize(project_dir: Dir.pwd, prompt: TTY::Prompt.new)
|
|
25
|
+
@project_dir = project_dir
|
|
26
|
+
@prompt = prompt
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Run tools command
|
|
30
|
+
#
|
|
31
|
+
# @param args [Array<String>] Command arguments
|
|
32
|
+
# @return [Integer] Exit code
|
|
33
|
+
def run(args)
|
|
34
|
+
subcommand = args.shift
|
|
35
|
+
|
|
36
|
+
case subcommand
|
|
37
|
+
when "lint"
|
|
38
|
+
run_lint
|
|
39
|
+
when "info"
|
|
40
|
+
tool_id = args.shift
|
|
41
|
+
unless tool_id
|
|
42
|
+
@prompt.say("Error: tool ID required")
|
|
43
|
+
@prompt.say("Usage: aidp tools info <tool_id>")
|
|
44
|
+
return 1
|
|
45
|
+
end
|
|
46
|
+
run_info(tool_id)
|
|
47
|
+
when "reload"
|
|
48
|
+
run_reload
|
|
49
|
+
when "list"
|
|
50
|
+
run_list
|
|
51
|
+
when nil, "help", "--help", "-h"
|
|
52
|
+
show_help
|
|
53
|
+
0
|
|
54
|
+
else
|
|
55
|
+
@prompt.say("Unknown subcommand: #{subcommand}")
|
|
56
|
+
show_help
|
|
57
|
+
1
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Show help message
|
|
62
|
+
def show_help
|
|
63
|
+
@prompt.say("\nAIDP Tools Management")
|
|
64
|
+
@prompt.say("\nUsage:")
|
|
65
|
+
@prompt.say(" aidp tools lint Validate all tool metadata")
|
|
66
|
+
@prompt.say(" aidp tools info <id> Show detailed tool information")
|
|
67
|
+
@prompt.say(" aidp tools reload Force regenerate tool directory cache")
|
|
68
|
+
@prompt.say(" aidp tools list List all available tools")
|
|
69
|
+
@prompt.say("\nExamples:")
|
|
70
|
+
@prompt.say(" aidp tools lint")
|
|
71
|
+
@prompt.say(" aidp tools info ruby_rspec_tdd")
|
|
72
|
+
@prompt.say(" aidp tools reload")
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Run lint command
|
|
76
|
+
def run_lint
|
|
77
|
+
Aidp.log_info("tools", "Running tool metadata lint")
|
|
78
|
+
|
|
79
|
+
@prompt.say("\nValidating tool metadata...")
|
|
80
|
+
|
|
81
|
+
cache = create_cache
|
|
82
|
+
query = Metadata::Query.new(cache: cache)
|
|
83
|
+
|
|
84
|
+
begin
|
|
85
|
+
query.directory
|
|
86
|
+
rescue => e
|
|
87
|
+
@prompt.error("Failed to load tool directory: #{e.message}")
|
|
88
|
+
Aidp.log_error("tools", "Lint failed", error: e.message)
|
|
89
|
+
return 1
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Reload to get fresh validation
|
|
93
|
+
tools = load_all_tools
|
|
94
|
+
|
|
95
|
+
validator = Metadata::Validator.new(tools)
|
|
96
|
+
results = validator.validate_all
|
|
97
|
+
|
|
98
|
+
# Display results
|
|
99
|
+
display_lint_results(results)
|
|
100
|
+
|
|
101
|
+
# Write error log if there are errors
|
|
102
|
+
invalid_results = results.reject(&:valid)
|
|
103
|
+
if invalid_results.any?
|
|
104
|
+
log_path = File.join(@project_dir, ".aidp", "logs", "metadata_errors.log")
|
|
105
|
+
validator.write_error_log(results, log_path)
|
|
106
|
+
@prompt.warn("\nError log written to: #{log_path}")
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
invalid_results.empty? ? 0 : 1
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Run info command
|
|
113
|
+
#
|
|
114
|
+
# @param tool_id [String] Tool ID
|
|
115
|
+
def run_info(tool_id)
|
|
116
|
+
Aidp.log_info("tools", "Showing tool info", tool_id: tool_id)
|
|
117
|
+
|
|
118
|
+
cache = create_cache
|
|
119
|
+
query = Metadata::Query.new(cache: cache)
|
|
120
|
+
|
|
121
|
+
tool = query.find_by_id(tool_id)
|
|
122
|
+
|
|
123
|
+
unless tool
|
|
124
|
+
@prompt.error("Tool not found: #{tool_id}")
|
|
125
|
+
return 1
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
display_tool_details(tool)
|
|
129
|
+
|
|
130
|
+
0
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# Run reload command
|
|
134
|
+
def run_reload
|
|
135
|
+
Aidp.log_info("tools", "Reloading tool directory")
|
|
136
|
+
|
|
137
|
+
@prompt.say("\nRegenerating tool directory cache...")
|
|
138
|
+
|
|
139
|
+
cache = create_cache
|
|
140
|
+
|
|
141
|
+
begin
|
|
142
|
+
cache.reload
|
|
143
|
+
@prompt.ok("Tool directory cache regenerated successfully")
|
|
144
|
+
rescue => e
|
|
145
|
+
@prompt.error("Failed to reload cache: #{e.message}")
|
|
146
|
+
Aidp.log_error("tools", "Reload failed", error: e.message)
|
|
147
|
+
return 1
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
0
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# Run list command
|
|
154
|
+
def run_list
|
|
155
|
+
Aidp.log_info("tools", "Listing all tools")
|
|
156
|
+
|
|
157
|
+
cache = create_cache
|
|
158
|
+
query = Metadata::Query.new(cache: cache)
|
|
159
|
+
|
|
160
|
+
query.directory
|
|
161
|
+
stats = query.statistics
|
|
162
|
+
|
|
163
|
+
@prompt.say("\nTool Directory Statistics:")
|
|
164
|
+
@prompt.say(" Total tools: #{stats["total_tools"]}")
|
|
165
|
+
@prompt.say(" By type:")
|
|
166
|
+
stats["by_type"].each do |type, count|
|
|
167
|
+
@prompt.say(" #{type}: #{count}")
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
@prompt.say("\nAll Tools:")
|
|
171
|
+
|
|
172
|
+
# Group by type
|
|
173
|
+
%w[skill persona template].each do |type|
|
|
174
|
+
tools = query.find_by_type(type)
|
|
175
|
+
next if tools.empty?
|
|
176
|
+
|
|
177
|
+
@prompt.say("\n#{type.capitalize}s (#{tools.size}):")
|
|
178
|
+
display_tools_table(tools)
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
0
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
private
|
|
185
|
+
|
|
186
|
+
# Create metadata cache instance
|
|
187
|
+
#
|
|
188
|
+
# @return [Metadata::Cache] Cache instance
|
|
189
|
+
def create_cache
|
|
190
|
+
tool_config = Aidp::Config.tool_metadata_config(@project_dir)
|
|
191
|
+
|
|
192
|
+
directories = tool_config[:directories] || default_directories
|
|
193
|
+
cache_file = tool_config[:cache_file] || default_cache_file
|
|
194
|
+
strict = tool_config[:strict] || false
|
|
195
|
+
|
|
196
|
+
Metadata::Cache.new(
|
|
197
|
+
cache_path: cache_file,
|
|
198
|
+
directories: directories,
|
|
199
|
+
strict: strict
|
|
200
|
+
)
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
# Get default directories to scan
|
|
204
|
+
#
|
|
205
|
+
# @return [Array<String>] Default directories
|
|
206
|
+
def default_directories
|
|
207
|
+
[
|
|
208
|
+
File.join(@project_dir, ".aidp", "skills"),
|
|
209
|
+
File.join(@project_dir, ".aidp", "personas"),
|
|
210
|
+
File.join(@project_dir, ".aidp", "templates"),
|
|
211
|
+
# Also include gem templates
|
|
212
|
+
File.expand_path("../../templates/skills", __dir__),
|
|
213
|
+
File.expand_path("../../templates", __dir__)
|
|
214
|
+
].select { |dir| Dir.exist?(dir) }
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
# Get default cache file path
|
|
218
|
+
#
|
|
219
|
+
# @return [String] Cache file path
|
|
220
|
+
def default_cache_file
|
|
221
|
+
File.join(@project_dir, ".aidp", "cache", "tool_directory.json")
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
# Load all tools from directories
|
|
225
|
+
#
|
|
226
|
+
# @return [Array<Metadata::ToolMetadata>] All tools
|
|
227
|
+
def load_all_tools
|
|
228
|
+
directories = default_directories
|
|
229
|
+
scanner = Metadata::Scanner.new(directories)
|
|
230
|
+
scanner.scan_all
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
# Display lint results
|
|
234
|
+
#
|
|
235
|
+
# @param results [Array<Metadata::Validator::ValidationResult>] Results
|
|
236
|
+
def display_lint_results(results)
|
|
237
|
+
valid_count = results.count(&:valid)
|
|
238
|
+
invalid_count = results.count { |r| !r.valid }
|
|
239
|
+
warning_count = results.sum { |r| r.warnings.size }
|
|
240
|
+
|
|
241
|
+
@prompt.say("\nValidation Results:")
|
|
242
|
+
@prompt.say(" Total tools: #{results.size}")
|
|
243
|
+
@prompt.ok(" Valid: #{valid_count}") if valid_count > 0
|
|
244
|
+
@prompt.error(" Invalid: #{invalid_count}") if invalid_count > 0
|
|
245
|
+
@prompt.warn(" Warnings: #{warning_count}") if warning_count > 0
|
|
246
|
+
|
|
247
|
+
# Show errors
|
|
248
|
+
invalid_results = results.reject(&:valid)
|
|
249
|
+
if invalid_results.any?
|
|
250
|
+
@prompt.say("\nErrors:")
|
|
251
|
+
invalid_results.each do |result|
|
|
252
|
+
@prompt.error("\n #{result.tool_id} (#{result.file_path}):")
|
|
253
|
+
result.errors.each { |err| @prompt.say(" - #{err}") }
|
|
254
|
+
end
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
# Show warnings
|
|
258
|
+
warnings = results.select { |r| r.warnings.any? }
|
|
259
|
+
if warnings.any?
|
|
260
|
+
@prompt.say("\nWarnings:")
|
|
261
|
+
warnings.each do |result|
|
|
262
|
+
@prompt.warn("\n #{result.tool_id} (#{result.file_path}):")
|
|
263
|
+
result.warnings.each { |warn| @prompt.say(" - #{warn}") }
|
|
264
|
+
end
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
if invalid_count.zero? && warning_count.zero?
|
|
268
|
+
@prompt.ok("\nAll tools validated successfully!")
|
|
269
|
+
end
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
# Display tool details
|
|
273
|
+
#
|
|
274
|
+
# @param tool [Hash] Tool metadata
|
|
275
|
+
def display_tool_details(tool)
|
|
276
|
+
@prompt.say("\nTool: #{tool["title"]}")
|
|
277
|
+
@prompt.say("=" * 60)
|
|
278
|
+
@prompt.say("ID: #{tool["id"]}")
|
|
279
|
+
@prompt.say("Type: #{tool["type"]}")
|
|
280
|
+
@prompt.say("Version: #{tool["version"]}")
|
|
281
|
+
@prompt.say("Summary: #{tool["summary"]}")
|
|
282
|
+
@prompt.say("Priority: #{tool["priority"]}")
|
|
283
|
+
@prompt.say("Experimental: #{tool["experimental"]}")
|
|
284
|
+
|
|
285
|
+
if tool["applies_to"]&.any?
|
|
286
|
+
@prompt.say("\nApplies To:")
|
|
287
|
+
tool["applies_to"].each { |tag| @prompt.say(" - #{tag}") }
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
if tool["work_unit_types"]&.any?
|
|
291
|
+
@prompt.say("\nWork Unit Types:")
|
|
292
|
+
tool["work_unit_types"].each { |wut| @prompt.say(" - #{wut}") }
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
if tool["capabilities"]&.any?
|
|
296
|
+
@prompt.say("\nCapabilities:")
|
|
297
|
+
tool["capabilities"].each { |cap| @prompt.say(" - #{cap}") }
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
if tool["dependencies"]&.any?
|
|
301
|
+
@prompt.say("\nDependencies:")
|
|
302
|
+
tool["dependencies"].each { |dep| @prompt.say(" - #{dep}") }
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
@prompt.say("\nSource: #{tool["source_path"]}")
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
# Display tools in a table
|
|
309
|
+
#
|
|
310
|
+
# @param tools [Array<Hash>] Tools to display
|
|
311
|
+
def display_tools_table(tools)
|
|
312
|
+
return if tools.empty?
|
|
313
|
+
|
|
314
|
+
rows = tools.map do |tool|
|
|
315
|
+
[
|
|
316
|
+
tool["id"],
|
|
317
|
+
tool["title"],
|
|
318
|
+
tool["version"],
|
|
319
|
+
tool["priority"],
|
|
320
|
+
(tool["applies_to"] || []).join(", ")
|
|
321
|
+
]
|
|
322
|
+
end
|
|
323
|
+
|
|
324
|
+
table = TTY::Table.new(
|
|
325
|
+
header: ["ID", "Title", "Version", "Priority", "Tags"],
|
|
326
|
+
rows: rows
|
|
327
|
+
)
|
|
328
|
+
|
|
329
|
+
@prompt.say(table.render(:unicode))
|
|
330
|
+
end
|
|
331
|
+
end
|
|
332
|
+
end
|
|
333
|
+
end
|