aidp 0.25.0 → 0.27.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/README.md +45 -6
- data/lib/aidp/analyze/error_handler.rb +11 -0
- data/lib/aidp/cli/checkpoint_command.rb +198 -0
- data/lib/aidp/cli/config_command.rb +71 -0
- data/lib/aidp/cli/enhanced_input.rb +2 -0
- data/lib/aidp/cli/first_run_wizard.rb +8 -7
- data/lib/aidp/cli/harness_command.rb +102 -0
- data/lib/aidp/cli/jobs_command.rb +3 -3
- data/lib/aidp/cli/mcp_dashboard.rb +4 -3
- data/lib/aidp/cli/models_command.rb +662 -0
- data/lib/aidp/cli/providers_command.rb +223 -0
- data/lib/aidp/cli.rb +35 -456
- data/lib/aidp/daemon/runner.rb +2 -2
- data/lib/aidp/debug_mixin.rb +2 -9
- data/lib/aidp/execute/async_work_loop_runner.rb +2 -1
- data/lib/aidp/execute/checkpoint_display.rb +38 -37
- data/lib/aidp/execute/interactive_repl.rb +2 -1
- data/lib/aidp/execute/prompt_manager.rb +4 -4
- data/lib/aidp/execute/work_loop_runner.rb +253 -56
- data/lib/aidp/execute/workflow_selector.rb +2 -2
- data/lib/aidp/harness/config_loader.rb +20 -11
- data/lib/aidp/harness/config_manager.rb +5 -5
- data/lib/aidp/harness/config_schema.rb +30 -8
- data/lib/aidp/harness/configuration.rb +105 -4
- data/lib/aidp/harness/enhanced_runner.rb +24 -15
- data/lib/aidp/harness/error_handler.rb +26 -5
- data/lib/aidp/harness/filter_strategy.rb +45 -0
- data/lib/aidp/harness/generic_filter_strategy.rb +63 -0
- data/lib/aidp/harness/model_cache.rb +269 -0
- data/lib/aidp/harness/model_discovery_service.rb +259 -0
- data/lib/aidp/harness/model_registry.rb +201 -0
- data/lib/aidp/harness/output_filter.rb +136 -0
- data/lib/aidp/harness/provider_manager.rb +18 -3
- data/lib/aidp/harness/rspec_filter_strategy.rb +82 -0
- data/lib/aidp/harness/runner.rb +5 -0
- data/lib/aidp/harness/test_runner.rb +165 -27
- data/lib/aidp/harness/thinking_depth_manager.rb +223 -7
- data/lib/aidp/harness/ui/enhanced_tui.rb +4 -1
- data/lib/aidp/logger.rb +35 -5
- data/lib/aidp/providers/adapter.rb +2 -4
- data/lib/aidp/providers/anthropic.rb +141 -128
- data/lib/aidp/providers/base.rb +98 -2
- data/lib/aidp/providers/capability_registry.rb +0 -1
- data/lib/aidp/providers/codex.rb +49 -67
- data/lib/aidp/providers/cursor.rb +71 -59
- data/lib/aidp/providers/gemini.rb +44 -60
- data/lib/aidp/providers/github_copilot.rb +2 -66
- data/lib/aidp/providers/kilocode.rb +24 -80
- data/lib/aidp/providers/opencode.rb +24 -80
- data/lib/aidp/safe_directory.rb +10 -3
- data/lib/aidp/setup/wizard.rb +345 -8
- data/lib/aidp/storage/csv_storage.rb +9 -3
- data/lib/aidp/storage/file_manager.rb +8 -2
- data/lib/aidp/storage/json_storage.rb +9 -3
- data/lib/aidp/version.rb +1 -1
- data/lib/aidp/watch/build_processor.rb +40 -1
- data/lib/aidp/watch/change_request_processor.rb +659 -0
- data/lib/aidp/watch/plan_generator.rb +93 -14
- data/lib/aidp/watch/plan_processor.rb +71 -8
- data/lib/aidp/watch/repository_client.rb +85 -20
- data/lib/aidp/watch/review_processor.rb +3 -3
- data/lib/aidp/watch/runner.rb +37 -0
- data/lib/aidp/watch/state_store.rb +46 -1
- data/lib/aidp/workflows/guided_agent.rb +3 -3
- data/lib/aidp/workstream_executor.rb +5 -2
- data/lib/aidp.rb +4 -0
- data/templates/aidp-development.yml.example +2 -2
- data/templates/aidp-production.yml.example +3 -3
- data/templates/aidp.yml.example +53 -0
- metadata +14 -1
data/lib/aidp/logger.rb
CHANGED
|
@@ -18,6 +18,27 @@ module Aidp
|
|
|
18
18
|
# Aidp.setup_logger(project_dir, config)
|
|
19
19
|
# Aidp.logger.info("component", "message", key: "value")
|
|
20
20
|
class Logger
|
|
21
|
+
# Custom log device that suppresses IOError exceptions for closed streams.
|
|
22
|
+
# This prevents "log shifting failed. closed stream" errors during test cleanup.
|
|
23
|
+
class SafeLogDevice < ::Logger::LogDevice
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
# Override handle_write_errors to suppress warnings for closed stream errors.
|
|
27
|
+
# Ruby's Logger::LogDevice calls warn() when write operations fail, which
|
|
28
|
+
# produces "log shifting failed. closed stream" messages during test cleanup.
|
|
29
|
+
def handle_write_errors(mesg)
|
|
30
|
+
yield
|
|
31
|
+
rescue *@reraise_write_errors
|
|
32
|
+
raise
|
|
33
|
+
rescue IOError => e
|
|
34
|
+
# Silently ignore closed stream errors - these are expected during
|
|
35
|
+
# test cleanup when loggers are finalized after streams are closed
|
|
36
|
+
return if e.message.include?("closed stream")
|
|
37
|
+
warn("log #{mesg} failed. #{e}")
|
|
38
|
+
rescue => e
|
|
39
|
+
warn("log #{mesg} failed. #{e}")
|
|
40
|
+
end
|
|
41
|
+
end
|
|
21
42
|
LEVELS = {
|
|
22
43
|
debug: ::Logger::DEBUG,
|
|
23
44
|
info: ::Logger::INFO,
|
|
@@ -140,14 +161,17 @@ module Aidp
|
|
|
140
161
|
dir = File.dirname(path)
|
|
141
162
|
FileUtils.mkdir_p(dir) unless Dir.exist?(dir)
|
|
142
163
|
|
|
143
|
-
logger
|
|
164
|
+
# Create logger with custom SafeLogDevice that suppresses closed stream errors
|
|
165
|
+
logdev = SafeLogDevice.new(path, shift_age: @max_files, shift_size: @max_size)
|
|
166
|
+
logger = ::Logger.new(logdev)
|
|
144
167
|
logger.level = ::Logger::DEBUG # Control at write level instead
|
|
145
168
|
logger.formatter = proc { |severity, datetime, progname, msg| "#{msg}\n" }
|
|
146
169
|
logger
|
|
147
170
|
rescue => e
|
|
148
171
|
# Fall back to STDERR if file logging fails
|
|
149
172
|
Kernel.warn "[AIDP Logger] Failed to create log file at #{path}: #{e.message}. Falling back to STDERR."
|
|
150
|
-
|
|
173
|
+
logdev = SafeLogDevice.new($stderr)
|
|
174
|
+
logger = ::Logger.new(logdev)
|
|
151
175
|
logger.level = ::Logger::DEBUG
|
|
152
176
|
logger.formatter = proc { |severity, datetime, progname, msg| "#{msg}\n" }
|
|
153
177
|
logger
|
|
@@ -246,7 +270,9 @@ module Aidp
|
|
|
246
270
|
raw_input = dir.to_s
|
|
247
271
|
raw_invalid = raw_input.empty? || raw_input.match?(/[<>|]/) || raw_input.match?(/[\x00-\x1F]/)
|
|
248
272
|
if raw_invalid
|
|
249
|
-
|
|
273
|
+
unless ENV["RSPEC_RUNNING"] == "true"
|
|
274
|
+
Kernel.warn "[AIDP Logger] Invalid project_dir '#{raw_input}' - falling back to #{Dir.pwd}"
|
|
275
|
+
end
|
|
250
276
|
if Dir.pwd == File::SEPARATOR
|
|
251
277
|
fallback = begin
|
|
252
278
|
home = Dir.home
|
|
@@ -255,7 +281,9 @@ module Aidp
|
|
|
255
281
|
Dir.tmpdir
|
|
256
282
|
end
|
|
257
283
|
@root_fallback = fallback
|
|
258
|
-
|
|
284
|
+
unless ENV["RSPEC_RUNNING"] == "true"
|
|
285
|
+
Kernel.warn "[AIDP Logger] Root directory detected - using #{fallback} for logging instead of '#{Dir.pwd}'"
|
|
286
|
+
end
|
|
259
287
|
return fallback
|
|
260
288
|
end
|
|
261
289
|
return Dir.pwd
|
|
@@ -268,7 +296,9 @@ module Aidp
|
|
|
268
296
|
Dir.tmpdir
|
|
269
297
|
end
|
|
270
298
|
@root_fallback = fallback
|
|
271
|
-
|
|
299
|
+
unless ENV["RSPEC_RUNNING"] == "true"
|
|
300
|
+
Kernel.warn "[AIDP Logger] Root directory detected - using #{fallback} for logging instead of '#{raw_input}'"
|
|
301
|
+
end
|
|
272
302
|
return fallback
|
|
273
303
|
end
|
|
274
304
|
raw_input
|
|
@@ -65,8 +65,7 @@ module Aidp
|
|
|
65
65
|
# supports_json_mode: true,
|
|
66
66
|
# supports_tool_use: true,
|
|
67
67
|
# supports_vision: false,
|
|
68
|
-
# supports_file_upload: true
|
|
69
|
-
# streaming: true
|
|
68
|
+
# supports_file_upload: true
|
|
70
69
|
# }
|
|
71
70
|
def capabilities
|
|
72
71
|
{
|
|
@@ -75,8 +74,7 @@ module Aidp
|
|
|
75
74
|
supports_json_mode: false,
|
|
76
75
|
supports_tool_use: false,
|
|
77
76
|
supports_vision: false,
|
|
78
|
-
supports_file_upload: false
|
|
79
|
-
streaming: false
|
|
77
|
+
supports_file_upload: false
|
|
80
78
|
}
|
|
81
79
|
end
|
|
82
80
|
|
|
@@ -9,10 +9,143 @@ module Aidp
|
|
|
9
9
|
class Anthropic < Base
|
|
10
10
|
include Aidp::DebugMixin
|
|
11
11
|
|
|
12
|
+
# Model name pattern for Anthropic Claude models
|
|
13
|
+
MODEL_PATTERN = /^claude-[\d.-]+-(?:opus|sonnet|haiku)(?:-\d{8})?$/i
|
|
14
|
+
|
|
12
15
|
def self.available?
|
|
13
16
|
!!Aidp::Util.which("claude")
|
|
14
17
|
end
|
|
15
18
|
|
|
19
|
+
# Normalize a provider-specific model name to its model family
|
|
20
|
+
#
|
|
21
|
+
# Anthropic uses date-versioned models (e.g., "claude-3-5-sonnet-20241022").
|
|
22
|
+
# This method strips the date suffix to get the family name.
|
|
23
|
+
#
|
|
24
|
+
# @param provider_model_name [String] The versioned model name
|
|
25
|
+
# @return [String] The model family name (e.g., "claude-3-5-sonnet")
|
|
26
|
+
def self.model_family(provider_model_name)
|
|
27
|
+
# Strip date suffix: "claude-3-5-sonnet-20241022" → "claude-3-5-sonnet"
|
|
28
|
+
provider_model_name.sub(/-\d{8}$/, "")
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Convert a model family name to the provider's preferred model name
|
|
32
|
+
#
|
|
33
|
+
# Returns the family name as-is. Users can configure specific versions in aidp.yml.
|
|
34
|
+
#
|
|
35
|
+
# @param family_name [String] The model family name
|
|
36
|
+
# @return [String] The model name (same as family for flexibility)
|
|
37
|
+
def self.provider_model_name(family_name)
|
|
38
|
+
family_name
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Check if this provider supports a given model family
|
|
42
|
+
#
|
|
43
|
+
# @param family_name [String] The model family name
|
|
44
|
+
# @return [Boolean] True if it matches Claude model pattern
|
|
45
|
+
def self.supports_model_family?(family_name)
|
|
46
|
+
MODEL_PATTERN.match?(family_name)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Discover available models from Claude CLI
|
|
50
|
+
#
|
|
51
|
+
# @return [Array<Hash>] Array of discovered models
|
|
52
|
+
def self.discover_models
|
|
53
|
+
return [] unless available?
|
|
54
|
+
|
|
55
|
+
begin
|
|
56
|
+
require "open3"
|
|
57
|
+
output, _, status = Open3.capture3("claude", "models", "list", {timeout: 10})
|
|
58
|
+
return [] unless status.success?
|
|
59
|
+
|
|
60
|
+
parse_models_list(output)
|
|
61
|
+
rescue => e
|
|
62
|
+
Aidp.log_debug("anthropic_provider", "discovery failed", error: e.message)
|
|
63
|
+
[]
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
class << self
|
|
68
|
+
private
|
|
69
|
+
|
|
70
|
+
def parse_models_list(output)
|
|
71
|
+
return [] if output.nil? || output.empty?
|
|
72
|
+
|
|
73
|
+
models = []
|
|
74
|
+
lines = output.lines.map(&:strip)
|
|
75
|
+
|
|
76
|
+
# Skip header and separator lines
|
|
77
|
+
lines.reject! { |line| line.empty? || line.match?(/^[-=]+$/) || line.match?(/^(Model|Name)/i) }
|
|
78
|
+
|
|
79
|
+
lines.each do |line|
|
|
80
|
+
model_info = parse_model_line(line)
|
|
81
|
+
models << model_info if model_info
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
Aidp.log_info("anthropic_provider", "discovered models", count: models.size)
|
|
85
|
+
models
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def parse_model_line(line)
|
|
89
|
+
# Format 1: Simple list of model names
|
|
90
|
+
if line.match?(/^claude-\d/)
|
|
91
|
+
model_name = line.split.first
|
|
92
|
+
return build_model_info(model_name)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Format 2: Table format with columns
|
|
96
|
+
parts = line.split(/\s{2,}/)
|
|
97
|
+
if parts.size >= 1 && parts[0].match?(/^claude/)
|
|
98
|
+
model_name = parts[0]
|
|
99
|
+
model_name = "#{model_name}-#{parts[1]}" if parts.size > 1 && parts[1].match?(/^\d{8}$/)
|
|
100
|
+
return build_model_info(model_name)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Format 3: JSON-like or key-value pairs
|
|
104
|
+
if line.match?(/name:\s*(.+)/)
|
|
105
|
+
model_name = $1.strip
|
|
106
|
+
return build_model_info(model_name)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
nil
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def build_model_info(model_name)
|
|
113
|
+
family = model_family(model_name)
|
|
114
|
+
tier = classify_tier(model_name)
|
|
115
|
+
|
|
116
|
+
{
|
|
117
|
+
name: model_name,
|
|
118
|
+
family: family,
|
|
119
|
+
tier: tier,
|
|
120
|
+
capabilities: extract_capabilities(model_name),
|
|
121
|
+
context_window: infer_context_window(family),
|
|
122
|
+
provider: "anthropic"
|
|
123
|
+
}
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def classify_tier(model_name)
|
|
127
|
+
name_lower = model_name.downcase
|
|
128
|
+
return "advanced" if name_lower.include?("opus")
|
|
129
|
+
return "mini" if name_lower.include?("haiku")
|
|
130
|
+
return "standard" if name_lower.include?("sonnet")
|
|
131
|
+
"standard"
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def extract_capabilities(model_name)
|
|
135
|
+
capabilities = ["chat", "code"]
|
|
136
|
+
name_lower = model_name.downcase
|
|
137
|
+
capabilities << "vision" unless name_lower.include?("haiku")
|
|
138
|
+
capabilities
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def infer_context_window(family)
|
|
142
|
+
family.match?(/claude-3/) ? 200_000 : nil
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# Public instance methods (called from workflows and harness)
|
|
147
|
+
public
|
|
148
|
+
|
|
16
149
|
def name
|
|
17
150
|
"anthropic"
|
|
18
151
|
end
|
|
@@ -53,8 +186,7 @@ module Aidp
|
|
|
53
186
|
supports_json_mode: true,
|
|
54
187
|
supports_tool_use: true,
|
|
55
188
|
supports_vision: false,
|
|
56
|
-
supports_file_upload: true
|
|
57
|
-
streaming: true
|
|
189
|
+
supports_file_upload: true
|
|
58
190
|
}
|
|
59
191
|
end
|
|
60
192
|
|
|
@@ -115,19 +247,8 @@ module Aidp
|
|
|
115
247
|
debug_provider("claude", "Starting execution", {timeout: timeout_seconds})
|
|
116
248
|
debug_log("📝 Sending prompt to claude...", level: :info)
|
|
117
249
|
|
|
118
|
-
#
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
# Build command arguments with proper streaming support
|
|
122
|
-
args = ["--print"]
|
|
123
|
-
if streaming_enabled
|
|
124
|
-
# Claude CLI requires --verbose when using --print with --output-format=stream-json
|
|
125
|
-
args += ["--verbose", "--output-format=stream-json", "--include-partial-messages"]
|
|
126
|
-
display_message("📺 True streaming enabled - real-time chunks from Claude API", type: :info)
|
|
127
|
-
else
|
|
128
|
-
# Use text format for non-streaming (default behavior)
|
|
129
|
-
args += ["--output-format=text"]
|
|
130
|
-
end
|
|
250
|
+
# Build command arguments
|
|
251
|
+
args = ["--print", "--output-format=text"]
|
|
131
252
|
|
|
132
253
|
# Check if we should skip permissions (devcontainer support)
|
|
133
254
|
if should_skip_permissions?
|
|
@@ -136,26 +257,20 @@ module Aidp
|
|
|
136
257
|
end
|
|
137
258
|
|
|
138
259
|
begin
|
|
139
|
-
|
|
140
|
-
result = debug_execute_command("claude", args: args, input: prompt, timeout: timeout_seconds, streaming: streaming_enabled)
|
|
260
|
+
result = debug_execute_command("claude", args: args, input: prompt, timeout: timeout_seconds)
|
|
141
261
|
|
|
142
262
|
# Log the results
|
|
143
263
|
debug_command("claude", args: args, input: prompt, output: result.out, error: result.err, exit_code: result.exit_status)
|
|
144
264
|
|
|
145
265
|
if result.exit_status == 0
|
|
146
|
-
|
|
147
|
-
if streaming_enabled && args.include?("--output-format=stream-json")
|
|
148
|
-
# Parse stream-json output and extract final content
|
|
149
|
-
parse_stream_json_output(result.out)
|
|
150
|
-
else
|
|
151
|
-
# Return text output as-is
|
|
152
|
-
result.out
|
|
153
|
-
end
|
|
266
|
+
result.out
|
|
154
267
|
else
|
|
155
268
|
# Detect auth issues in stdout/stderr (Claude sometimes prints JSON with auth error to stdout)
|
|
156
269
|
combined = [result.out, result.err].compact.join("\n")
|
|
157
270
|
if combined.downcase.include?("oauth token has expired") || combined.downcase.include?("authentication_error")
|
|
158
|
-
error_message = "Authentication error from Claude CLI: token expired or invalid
|
|
271
|
+
error_message = "Authentication error from Claude CLI: token expired or invalid.\n" \
|
|
272
|
+
"Run 'claude /login' or refresh credentials.\n" \
|
|
273
|
+
"Note: Model discovery requires valid authentication."
|
|
159
274
|
debug_error(StandardError.new(error_message), {exit_code: result.exit_status, stdout: result.out, stderr: result.err})
|
|
160
275
|
# Raise a recognizable error for classifier
|
|
161
276
|
raise error_message
|
|
@@ -172,60 +287,6 @@ module Aidp
|
|
|
172
287
|
|
|
173
288
|
private
|
|
174
289
|
|
|
175
|
-
def calculate_timeout
|
|
176
|
-
# Priority order for timeout calculation:
|
|
177
|
-
# 1. Quick mode (for testing)
|
|
178
|
-
# 2. Environment variable override
|
|
179
|
-
# 3. Adaptive timeout based on step type
|
|
180
|
-
# 4. Default timeout
|
|
181
|
-
|
|
182
|
-
if ENV["AIDP_QUICK_MODE"]
|
|
183
|
-
display_message("⚡ Quick mode enabled - #{TIMEOUT_QUICK_MODE / 60} minute timeout", type: :highlight)
|
|
184
|
-
return TIMEOUT_QUICK_MODE
|
|
185
|
-
end
|
|
186
|
-
|
|
187
|
-
if ENV["AIDP_ANTHROPIC_TIMEOUT"]
|
|
188
|
-
return ENV["AIDP_ANTHROPIC_TIMEOUT"].to_i
|
|
189
|
-
end
|
|
190
|
-
|
|
191
|
-
if adaptive_timeout
|
|
192
|
-
display_message("🧠 Using adaptive timeout: #{adaptive_timeout} seconds", type: :info)
|
|
193
|
-
return adaptive_timeout
|
|
194
|
-
end
|
|
195
|
-
|
|
196
|
-
# Default timeout
|
|
197
|
-
display_message("📋 Using default timeout: #{TIMEOUT_DEFAULT / 60} minutes", type: :info)
|
|
198
|
-
TIMEOUT_DEFAULT
|
|
199
|
-
end
|
|
200
|
-
|
|
201
|
-
def adaptive_timeout
|
|
202
|
-
@adaptive_timeout ||= begin
|
|
203
|
-
# Timeout recommendations based on step type patterns
|
|
204
|
-
step_name = ENV["AIDP_CURRENT_STEP"] || ""
|
|
205
|
-
|
|
206
|
-
case step_name
|
|
207
|
-
when /REPOSITORY_ANALYSIS/
|
|
208
|
-
TIMEOUT_REPOSITORY_ANALYSIS
|
|
209
|
-
when /ARCHITECTURE_ANALYSIS/
|
|
210
|
-
TIMEOUT_ARCHITECTURE_ANALYSIS
|
|
211
|
-
when /TEST_ANALYSIS/
|
|
212
|
-
TIMEOUT_TEST_ANALYSIS
|
|
213
|
-
when /FUNCTIONALITY_ANALYSIS/
|
|
214
|
-
TIMEOUT_FUNCTIONALITY_ANALYSIS
|
|
215
|
-
when /DOCUMENTATION_ANALYSIS/
|
|
216
|
-
TIMEOUT_DOCUMENTATION_ANALYSIS
|
|
217
|
-
when /STATIC_ANALYSIS/
|
|
218
|
-
TIMEOUT_STATIC_ANALYSIS
|
|
219
|
-
when /REFACTORING_RECOMMENDATIONS/
|
|
220
|
-
TIMEOUT_REFACTORING_RECOMMENDATIONS
|
|
221
|
-
when /IMPLEMENTATION/
|
|
222
|
-
TIMEOUT_IMPLEMENTATION
|
|
223
|
-
else
|
|
224
|
-
nil # Use default
|
|
225
|
-
end
|
|
226
|
-
end
|
|
227
|
-
end
|
|
228
|
-
|
|
229
290
|
# Check if we should skip permissions based on devcontainer configuration
|
|
230
291
|
# Overrides base class to add logging and Claude-specific config check
|
|
231
292
|
def should_skip_permissions?
|
|
@@ -243,54 +304,6 @@ module Aidp
|
|
|
243
304
|
false
|
|
244
305
|
end
|
|
245
306
|
|
|
246
|
-
# Parse stream-json output from Claude CLI
|
|
247
|
-
def parse_stream_json_output(output)
|
|
248
|
-
return output if output.nil? || output.empty?
|
|
249
|
-
|
|
250
|
-
# Stream-json output contains multiple JSON objects, one per line
|
|
251
|
-
# We want to extract the final content from the last complete message
|
|
252
|
-
lines = output.strip.split("\n")
|
|
253
|
-
content_parts = []
|
|
254
|
-
|
|
255
|
-
lines.each do |line|
|
|
256
|
-
next if line.strip.empty?
|
|
257
|
-
|
|
258
|
-
begin
|
|
259
|
-
json_obj = JSON.parse(line)
|
|
260
|
-
|
|
261
|
-
# Look for content in various possible structures
|
|
262
|
-
if json_obj["type"] == "content_block_delta" && json_obj["delta"] && json_obj["delta"]["text"]
|
|
263
|
-
content_parts << json_obj["delta"]["text"]
|
|
264
|
-
elsif json_obj["content"]&.is_a?(Array)
|
|
265
|
-
json_obj["content"].each do |content_item|
|
|
266
|
-
content_parts << content_item["text"] if content_item["text"]
|
|
267
|
-
end
|
|
268
|
-
elsif json_obj["message"] && json_obj["message"]["content"]
|
|
269
|
-
if json_obj["message"]["content"].is_a?(Array)
|
|
270
|
-
json_obj["message"]["content"].each do |content_item|
|
|
271
|
-
content_parts << content_item["text"] if content_item["text"]
|
|
272
|
-
end
|
|
273
|
-
elsif json_obj["message"]["content"].is_a?(String)
|
|
274
|
-
content_parts << json_obj["message"]["content"]
|
|
275
|
-
end
|
|
276
|
-
end
|
|
277
|
-
rescue JSON::ParserError => e
|
|
278
|
-
debug_log("⚠️ Failed to parse JSON line: #{e.message}", level: :warn, data: {line: line})
|
|
279
|
-
# If JSON parsing fails, treat as plain text
|
|
280
|
-
content_parts << line
|
|
281
|
-
end
|
|
282
|
-
end
|
|
283
|
-
|
|
284
|
-
result = content_parts.join
|
|
285
|
-
|
|
286
|
-
# Fallback: if no content found in JSON, return original output
|
|
287
|
-
result.empty? ? output : result
|
|
288
|
-
rescue => e
|
|
289
|
-
debug_log("⚠️ Failed to parse stream-json output: #{e.message}", level: :warn)
|
|
290
|
-
# Return original output if parsing fails
|
|
291
|
-
output
|
|
292
|
-
end
|
|
293
|
-
|
|
294
307
|
# Parse Claude MCP server list output
|
|
295
308
|
def parse_claude_mcp_output(output)
|
|
296
309
|
servers = []
|
data/lib/aidp/providers/base.rb
CHANGED
|
@@ -324,6 +324,42 @@ module Aidp
|
|
|
324
324
|
end
|
|
325
325
|
end
|
|
326
326
|
|
|
327
|
+
# Helper method for registry-based model discovery
|
|
328
|
+
#
|
|
329
|
+
# Providers that use the model registry can call this method to discover models
|
|
330
|
+
# based on a model family pattern.
|
|
331
|
+
#
|
|
332
|
+
# @param model_pattern [Regexp] Pattern to match model families
|
|
333
|
+
# @param provider_name [String] Name of the provider
|
|
334
|
+
# @return [Array<Hash>] Array of discovered models
|
|
335
|
+
def self.discover_models_from_registry(model_pattern, provider_name)
|
|
336
|
+
require_relative "../harness/model_registry"
|
|
337
|
+
registry = Aidp::Harness::ModelRegistry.new
|
|
338
|
+
|
|
339
|
+
# Get all models from registry that match the pattern
|
|
340
|
+
models = registry.all_families.filter_map do |family|
|
|
341
|
+
next unless model_pattern.match?(family)
|
|
342
|
+
|
|
343
|
+
info = registry.get_model_info(family)
|
|
344
|
+
next unless info
|
|
345
|
+
|
|
346
|
+
{
|
|
347
|
+
name: family,
|
|
348
|
+
family: family,
|
|
349
|
+
tier: info["tier"],
|
|
350
|
+
capabilities: info["capabilities"] || [],
|
|
351
|
+
context_window: info["context_window"],
|
|
352
|
+
provider: provider_name
|
|
353
|
+
}
|
|
354
|
+
end
|
|
355
|
+
|
|
356
|
+
Aidp.log_info("#{provider_name}_provider", "using registry models", count: models.size)
|
|
357
|
+
models
|
|
358
|
+
rescue => e
|
|
359
|
+
Aidp.log_debug("#{provider_name}_provider", "discovery failed", error: e.message)
|
|
360
|
+
[]
|
|
361
|
+
end
|
|
362
|
+
|
|
327
363
|
protected
|
|
328
364
|
|
|
329
365
|
# Log message to job if in background mode
|
|
@@ -371,8 +407,6 @@ module Aidp
|
|
|
371
407
|
(success_rate * 50) + ((1 - rate_limit_ratio) * 30) + (response_time_score * 0.2)
|
|
372
408
|
end
|
|
373
409
|
|
|
374
|
-
protected
|
|
375
|
-
|
|
376
410
|
# Update spinner status with elapsed time
|
|
377
411
|
# This is a shared method used by all providers to display progress
|
|
378
412
|
def update_spinner_status(spinner, elapsed, provider_name)
|
|
@@ -419,6 +453,68 @@ module Aidp
|
|
|
419
453
|
false # Base implementation returns false, subclasses should override
|
|
420
454
|
end
|
|
421
455
|
|
|
456
|
+
# Calculate timeout for provider operations
|
|
457
|
+
#
|
|
458
|
+
# Priority order:
|
|
459
|
+
# 1. Quick mode (for testing)
|
|
460
|
+
# 2. Provider-specific environment variable override
|
|
461
|
+
# 3. Adaptive timeout based on step type
|
|
462
|
+
# 4. Default timeout
|
|
463
|
+
#
|
|
464
|
+
# Override provider_env_var to customize the environment variable name
|
|
465
|
+
def calculate_timeout
|
|
466
|
+
if ENV["AIDP_QUICK_MODE"]
|
|
467
|
+
display_message("⚡ Quick mode enabled - #{TIMEOUT_QUICK_MODE / 60} minute timeout", type: :highlight)
|
|
468
|
+
return TIMEOUT_QUICK_MODE
|
|
469
|
+
end
|
|
470
|
+
|
|
471
|
+
provider_env_var = "AIDP_#{name.upcase}_TIMEOUT"
|
|
472
|
+
return ENV[provider_env_var].to_i if ENV[provider_env_var]
|
|
473
|
+
|
|
474
|
+
step_timeout = adaptive_timeout
|
|
475
|
+
if step_timeout
|
|
476
|
+
display_message("🧠 Using adaptive timeout: #{step_timeout} seconds", type: :info)
|
|
477
|
+
return step_timeout
|
|
478
|
+
end
|
|
479
|
+
|
|
480
|
+
# Default timeout
|
|
481
|
+
display_message("📋 Using default timeout: #{TIMEOUT_DEFAULT / 60} minutes", type: :info)
|
|
482
|
+
TIMEOUT_DEFAULT
|
|
483
|
+
end
|
|
484
|
+
|
|
485
|
+
# Get adaptive timeout based on step type
|
|
486
|
+
#
|
|
487
|
+
# This method returns different timeout values based on the type of operation
|
|
488
|
+
# being performed, as indicated by the AIDP_CURRENT_STEP environment variable.
|
|
489
|
+
# Returns nil for unknown steps to allow calculate_timeout to use the default.
|
|
490
|
+
def adaptive_timeout
|
|
491
|
+
@adaptive_timeout ||= begin
|
|
492
|
+
# Timeout recommendations based on step type patterns
|
|
493
|
+
step_name = ENV["AIDP_CURRENT_STEP"] || ""
|
|
494
|
+
|
|
495
|
+
case step_name
|
|
496
|
+
when /REPOSITORY_ANALYSIS/
|
|
497
|
+
TIMEOUT_REPOSITORY_ANALYSIS
|
|
498
|
+
when /ARCHITECTURE_ANALYSIS/
|
|
499
|
+
TIMEOUT_ARCHITECTURE_ANALYSIS
|
|
500
|
+
when /TEST_ANALYSIS/
|
|
501
|
+
TIMEOUT_TEST_ANALYSIS
|
|
502
|
+
when /FUNCTIONALITY_ANALYSIS/
|
|
503
|
+
TIMEOUT_FUNCTIONALITY_ANALYSIS
|
|
504
|
+
when /DOCUMENTATION_ANALYSIS/
|
|
505
|
+
TIMEOUT_DOCUMENTATION_ANALYSIS
|
|
506
|
+
when /STATIC_ANALYSIS/
|
|
507
|
+
TIMEOUT_STATIC_ANALYSIS
|
|
508
|
+
when /REFACTORING_RECOMMENDATIONS/
|
|
509
|
+
TIMEOUT_REFACTORING_RECOMMENDATIONS
|
|
510
|
+
when /IMPLEMENTATION/
|
|
511
|
+
TIMEOUT_IMPLEMENTATION
|
|
512
|
+
else
|
|
513
|
+
nil # Return nil for unknown steps
|
|
514
|
+
end
|
|
515
|
+
end
|
|
516
|
+
end
|
|
517
|
+
|
|
422
518
|
private
|
|
423
519
|
end
|
|
424
520
|
end
|
|
@@ -16,7 +16,6 @@ module Aidp
|
|
|
16
16
|
:supports_tool_use, # Boolean: supports tool/function calling
|
|
17
17
|
:supports_vision, # Boolean: supports image/vision inputs
|
|
18
18
|
:supports_file_upload, # Boolean: supports file uploads
|
|
19
|
-
:streaming, # Boolean: supports streaming responses
|
|
20
19
|
:supports_mcp, # Boolean: supports Model Context Protocol
|
|
21
20
|
:max_tokens, # Maximum tokens per response
|
|
22
21
|
:supports_dangerous_mode # Boolean: supports elevated permissions mode
|