Class: Aidp::Config

Inherits:
Object
  • Object
show all
Defined in:
lib/aidp/config.rb

Overview

Configuration management for both execute and analyze modes

Constant Summary collapse

DEFAULT_HARNESS_CONFIG =

Default configuration for harness

{
  harness: {
    max_retries: 2,
    default_provider: "cursor",
    fallback_providers: ["cursor"],
    no_api_keys_required: false,
    provider_weights: {
      "cursor" => 3,
      "anthropic" => 2
    },
    circuit_breaker: {
      enabled: true,
      failure_threshold: 5,
      timeout: 300,
      half_open_max_calls: 3
    },
    retry: {
      enabled: true,
      max_attempts: 3,
      base_delay: 1.0,
      max_delay: 60.0,
      exponential_base: 2.0,
      jitter: true
    },
    rate_limit: {
      enabled: true,
      default_reset_time: 3600,
      burst_limit: 10,
      sustained_limit: 5
    },
    load_balancing: {
      enabled: true,
      strategy: "weighted_round_robin",
      health_check_interval: 30,
      unhealthy_threshold: 3
    },
    model_switching: {
      enabled: true,
      auto_switch_on_error: true,
      auto_switch_on_rate_limit: true,
      fallback_strategy: "sequential"
    },
    health_check: {
      enabled: true,
      interval: 60,
      timeout: 10,
      failure_threshold: 3,
      success_threshold: 2
    },
    metrics: {
      enabled: true,
      retention_days: 30,
      aggregation_interval: 300,
      export_interval: 3600
    },
    session: {
      enabled: true,
      timeout: 1800,
      sticky_sessions: true,
      session_affinity: "provider_model"
    }
  },
  providers: {
    cursor: {
      type: "subscription",
      priority: 1,
      model_family: "auto",
      default_flags: [],
      models: ["cursor-default", "cursor-fast", "cursor-precise"],
      model_weights: {
        "cursor-default" => 3,
        "cursor-fast" => 2,
        "cursor-precise" => 1
      },
      models_config: {
        "cursor-default" => {
          flags: [],
          timeout: 600
        },
        "cursor-fast" => {
          flags: ["--fast"],
          timeout: 300
        },
        "cursor-precise" => {
          flags: ["--precise"],
          timeout: 900
        }
      },
      features: {
        file_upload: true,
        code_generation: true,
        analysis: true
      },
      monitoring: {
        enabled: true,
        metrics_interval: 60
      }
    },
    anthropic: {
      type: "usage_based",
      priority: 2,
      model_family: "claude",
      max_tokens: 100_000,
      default_flags: ["--dangerously-skip-permissions"],
      models: ["claude-3-5-sonnet-20241022", "claude-3-5-haiku-20241022"],
      model_weights: {
        "claude-3-5-sonnet-20241022" => 3,
        "claude-3-5-haiku-20241022" => 2
      },
      models_config: {
        "claude-3-5-sonnet-20241022" => {
          flags: ["--dangerously-skip-permissions"],
          max_tokens: 200_000,
          timeout: 300
        },
        "claude-3-5-haiku-20241022" => {
          flags: ["--dangerously-skip-permissions"],
          max_tokens: 200_000,
          timeout: 180
        }
      },
      auth: {
        api_key_env: "ANTHROPIC_API_KEY"
      },
      endpoints: {
        default: "https://api.anthropic.com/v1/messages"
      },
      features: {
        file_upload: true,
        code_generation: true,
        analysis: true,
        vision: true
      },
      monitoring: {
        enabled: true,
        metrics_interval: 60
      }
    }
  },
  skills: {
    search_paths: [],
    default_provider_filter: true,
    enable_custom_skills: true
  },
  waterfall: {
    enabled: true,
    docs_directory: ".aidp/docs",
    generate_decisions_md: true,
    gantt_format: "mermaid",
    wbs_phases: [
      "Requirements",
      "Design",
      "Implementation",
      "Testing",
      "Deployment"
    ],
    effort_estimation: {
      method: "llm_relative",
      units: "story_points"
    },
    persona_assignment: {
      method: "zfc_automatic",
      allow_parallel: true
    }
  }
}.freeze

Class Method Summary collapse

Class Method Details

.agile_config(project_dir = Dir.pwd) ⇒ Object

Get agile configuration



274
275
276
277
278
279
280
# File 'lib/aidp/config.rb', line 274

def self.agile_config(project_dir = Dir.pwd)
  config = load_harness_config(project_dir)
  agile_section = config[:agile] || config["agile"] || {}

  # Convert string keys to symbols for consistency
  symbolize_keys(agile_section)
end

.aidp_dir(project_dir = Dir.pwd) ⇒ Object



350
351
352
# File 'lib/aidp/config.rb', line 350

def self.aidp_dir(project_dir = Dir.pwd)
  ConfigPaths.aidp_dir(project_dir)
end

.config_dir(project_dir = Dir.pwd) ⇒ Object



346
347
348
# File 'lib/aidp/config.rb', line 346

def self.config_dir(project_dir = Dir.pwd)
  ConfigPaths.config_dir(project_dir)
end

.config_exists?(project_dir = Dir.pwd) ⇒ Boolean

Check if configuration file exists

Returns:

  • (Boolean)


292
293
294
# File 'lib/aidp/config.rb', line 292

def self.config_exists?(project_dir = Dir.pwd)
  ConfigPaths.config_exists?(project_dir)
end

.config_file(project_dir = Dir.pwd) ⇒ Object

Expose path methods for convenience



342
343
344
# File 'lib/aidp/config.rb', line 342

def self.config_file(project_dir = Dir.pwd)
  ConfigPaths.config_file(project_dir)
end

.configured_providers(project_dir = Dir.pwd) ⇒ Object

Get all configured providers



249
250
251
252
253
# File 'lib/aidp/config.rb', line 249

def self.configured_providers(project_dir = Dir.pwd)
  config = load_harness_config(project_dir)
  providers_section = config[:providers] || config["providers"] || {}
  providers_section.keys.map(&:to_s)
end

.create_example_config(project_dir = Dir.pwd) ⇒ Object

Create example configuration file



297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
# File 'lib/aidp/config.rb', line 297

def self.create_example_config(project_dir = Dir.pwd)
  config_path = ConfigPaths.config_file(project_dir)
  return false if File.exist?(config_path)

  ConfigPaths.ensure_config_dir(project_dir)

  example_config = {
    harness: {
      max_retries: 2,
      default_provider: "cursor",
      fallback_providers: ["cursor"],
      no_api_keys_required: false
    },
    providers: {
      cursor: {
        type: "subscription",
        default_flags: []
      },
      claude: {
        type: "usage_based",
        max_tokens: 100_000,
        default_flags: ["--dangerously-skip-permissions"]
      },
      gemini: {
        type: "usage_based",
        max_tokens: 50_000,
        default_flags: []
      }
    },
    agile: {
      mvp_first: true,
      feedback_loops: true,
      auto_iteration: false,
      research_enabled: true,
      marketing_enabled: true,
      legacy_analysis: true,
      personas: ["product_manager", "ux_researcher", "architect", "senior_developer", "qa_engineer", "devops_engineer", "tech_writer", "marketing_strategist"]
    }
  }

  File.write(config_path, YAML.dump(example_config))
  true
end

.harness_config(project_dir = Dir.pwd) ⇒ Object

Get harness configuration



231
232
233
234
235
236
237
# File 'lib/aidp/config.rb', line 231

def self.harness_config(project_dir = Dir.pwd)
  config = load_harness_config(project_dir)
  harness_section = config[:harness] || config["harness"] || {}

  # Convert string keys to symbols for consistency
  symbolize_keys(harness_section)
end

.load(project_dir = Dir.pwd) ⇒ Object



177
178
179
180
181
182
183
184
185
# File 'lib/aidp/config.rb', line 177

def self.load(project_dir = Dir.pwd)
  config_file = ConfigPaths.config_file(project_dir)

  if File.exist?(config_file)
    load_yaml_config(config_file)
  else
    {}
  end
end

.load_harness_config(project_dir = Dir.pwd) ⇒ Object

Load harness configuration with defaults



188
189
190
191
# File 'lib/aidp/config.rb', line 188

def self.load_harness_config(project_dir = Dir.pwd)
  config = load(project_dir)
  merge_harness_defaults(config)
end

.provider_config(provider_name, project_dir = Dir.pwd) ⇒ Object

Get provider configuration



240
241
242
243
244
245
246
# File 'lib/aidp/config.rb', line 240

def self.provider_config(provider_name, project_dir = Dir.pwd)
  config = load_harness_config(project_dir)
  providers_section = config[:providers] || config["providers"] || {}
  provider_config = providers_section[provider_name.to_s] || providers_section[provider_name.to_sym] || {}

  symbolize_keys(provider_config)
end

.skills_config(project_dir = Dir.pwd) ⇒ Object

Get skills configuration



256
257
258
259
260
261
262
# File 'lib/aidp/config.rb', line 256

def self.skills_config(project_dir = Dir.pwd)
  config = load_harness_config(project_dir)
  skills_section = config[:skills] || config["skills"] || {}

  # Convert string keys to symbols for consistency
  symbolize_keys(skills_section)
end

.tool_metadata_config(project_dir = Dir.pwd) ⇒ Object

Get tool metadata configuration



283
284
285
286
287
288
289
# File 'lib/aidp/config.rb', line 283

def self.(project_dir = Dir.pwd)
  config = load_harness_config(project_dir)
   = config[:tool_metadata] || config["tool_metadata"] || {}

  # Convert string keys to symbols for consistency
  symbolize_keys()
end

.validate_harness_config(config, project_dir = Dir.pwd) ⇒ Object

Validate harness configuration



194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
# File 'lib/aidp/config.rb', line 194

def self.validate_harness_config(config, project_dir = Dir.pwd)
  errors = []

  # Validate harness section (check the merged config, not original)
  harness_config = config[:harness] || config["harness"]
  if harness_config
    unless harness_config[:default_provider] || harness_config["default_provider"]
      errors << "Default provider not specified in harness config"
    end
  end

  # Validate providers section using config_validator
  # Only validate providers that exist in the original YAML file, not merged defaults
  original_config = load(project_dir)
  original_providers = original_config[:providers] || original_config["providers"]
  if original_providers&.any?
    require_relative "harness/config_validator"
    validator = Aidp::Harness::ConfigValidator.new(project_dir)

    # Only validate if the config file exists
    # Skip validation if we're validating a simple test config (no project_dir specified or simple config)
    should_validate = validator.config_exists? &&
      (project_dir != Dir.pwd || config[:harness]&.keys&.size.to_i > 2)
    if should_validate
      original_providers.each do |provider_name, _provider_config|
        validation_result = validator.validate_provider(provider_name)
        unless validation_result[:valid]
          errors.concat(validation_result[:errors])
        end
      end
    end
  end

  errors
end

.waterfall_config(project_dir = Dir.pwd) ⇒ Object

Get waterfall configuration



265
266
267
268
269
270
271
# File 'lib/aidp/config.rb', line 265

def self.waterfall_config(project_dir = Dir.pwd)
  config = load_harness_config(project_dir)
  waterfall_section = config[:waterfall] || config["waterfall"] || {}

  # Convert string keys to symbols for consistency
  symbolize_keys(waterfall_section)
end