aidp 0.24.0 → 0.25.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 (39) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +27 -1
  3. data/lib/aidp/auto_update/bundler_adapter.rb +66 -0
  4. data/lib/aidp/auto_update/checkpoint.rb +178 -0
  5. data/lib/aidp/auto_update/checkpoint_store.rb +182 -0
  6. data/lib/aidp/auto_update/coordinator.rb +204 -0
  7. data/lib/aidp/auto_update/errors.rb +17 -0
  8. data/lib/aidp/auto_update/failure_tracker.rb +162 -0
  9. data/lib/aidp/auto_update/rubygems_api_adapter.rb +95 -0
  10. data/lib/aidp/auto_update/update_check.rb +106 -0
  11. data/lib/aidp/auto_update/update_logger.rb +143 -0
  12. data/lib/aidp/auto_update/update_policy.rb +109 -0
  13. data/lib/aidp/auto_update/version_detector.rb +144 -0
  14. data/lib/aidp/auto_update.rb +52 -0
  15. data/lib/aidp/cli.rb +165 -1
  16. data/lib/aidp/harness/config_schema.rb +50 -0
  17. data/lib/aidp/harness/provider_factory.rb +2 -0
  18. data/lib/aidp/message_display.rb +10 -2
  19. data/lib/aidp/prompt_optimization/style_guide_indexer.rb +3 -1
  20. data/lib/aidp/provider_manager.rb +2 -0
  21. data/lib/aidp/providers/kilocode.rb +202 -0
  22. data/lib/aidp/setup/provider_registry.rb +15 -0
  23. data/lib/aidp/setup/wizard.rb +12 -4
  24. data/lib/aidp/skills/composer.rb +4 -0
  25. data/lib/aidp/skills/loader.rb +3 -1
  26. data/lib/aidp/version.rb +1 -1
  27. data/lib/aidp/watch/build_processor.rb +66 -16
  28. data/lib/aidp/watch/ci_fix_processor.rb +448 -0
  29. data/lib/aidp/watch/plan_processor.rb +12 -2
  30. data/lib/aidp/watch/repository_client.rb +380 -0
  31. data/lib/aidp/watch/review_processor.rb +266 -0
  32. data/lib/aidp/watch/reviewers/base_reviewer.rb +164 -0
  33. data/lib/aidp/watch/reviewers/performance_reviewer.rb +65 -0
  34. data/lib/aidp/watch/reviewers/security_reviewer.rb +65 -0
  35. data/lib/aidp/watch/reviewers/senior_dev_reviewer.rb +33 -0
  36. data/lib/aidp/watch/runner.rb +185 -0
  37. data/lib/aidp/watch/state_store.rb +53 -0
  38. data/lib/aidp.rb +1 -0
  39. metadata +20 -1
@@ -0,0 +1,144 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "update_check"
4
+ require_relative "update_policy"
5
+ require_relative "bundler_adapter"
6
+ require_relative "rubygems_api_adapter"
7
+
8
+ module Aidp
9
+ module AutoUpdate
10
+ # Service for detecting available gem versions and enforcing semver policy
11
+ class VersionDetector
12
+ def initialize(
13
+ policy:, current_version: Aidp::VERSION,
14
+ bundler_adapter: BundlerAdapter.new,
15
+ rubygems_adapter: RubyGemsAPIAdapter.new
16
+ )
17
+ @current_version = Gem::Version.new(current_version)
18
+ @policy = policy
19
+ @bundler_adapter = bundler_adapter
20
+ @rubygems_adapter = rubygems_adapter
21
+ end
22
+
23
+ # Check for updates according to policy
24
+ # @return [UpdateCheck] Result of update check
25
+ def check_for_update
26
+ Aidp.log_info("version_detector", "checking_for_updates",
27
+ current_version: @current_version.to_s,
28
+ policy: @policy.policy)
29
+
30
+ available_version = fetch_latest_version
31
+
32
+ unless available_version
33
+ Aidp.log_warn("version_detector", "no_version_available")
34
+ return UpdateCheck.unavailable(current_version: @current_version.to_s)
35
+ end
36
+
37
+ update_available = available_version > @current_version
38
+ # Check policy even if disabled - we still want to report update_available
39
+ update_allowed = @policy.disabled? ? false : update_allowed_by_policy?(available_version)
40
+ reason = policy_reason(available_version)
41
+
42
+ Aidp.log_info("version_detector", "update_check_complete",
43
+ current: @current_version.to_s,
44
+ available: available_version.to_s,
45
+ update_available: update_available,
46
+ update_allowed: update_allowed,
47
+ reason: reason)
48
+
49
+ UpdateCheck.new(
50
+ current_version: @current_version.to_s,
51
+ available_version: available_version.to_s,
52
+ update_available: update_available,
53
+ update_allowed: update_allowed,
54
+ policy_reason: reason
55
+ )
56
+ rescue => e
57
+ Aidp.log_error("version_detector", "check_failed", error: e.message)
58
+ UpdateCheck.failed(e.message, current_version: @current_version.to_s)
59
+ end
60
+
61
+ private
62
+
63
+ # Fetch latest version using bundler first, fallback to RubyGems API
64
+ # @return [Gem::Version, nil]
65
+ def fetch_latest_version
66
+ # Try bundler first
67
+ version = @bundler_adapter.latest_version_for("aidp")
68
+ return version if version
69
+
70
+ # Fallback to RubyGems API
71
+ Aidp.log_debug("version_detector", "falling_back_to_rubygems_api")
72
+ @rubygems_adapter.latest_version_for("aidp", allow_prerelease: @policy.allow_prerelease)
73
+ end
74
+
75
+ # Apply semver policy to determine if update is allowed
76
+ # @param available [Gem::Version] Available version
77
+ # @return [Boolean] Whether update is permitted
78
+ def update_allowed_by_policy?(available)
79
+ case @policy.policy
80
+ when "off"
81
+ false
82
+ when "exact"
83
+ # Only update if exact match (essentially disabled)
84
+ available == @current_version
85
+ when "patch"
86
+ # Allow patch updates within same major.minor
87
+ same_major_minor?(available) && available >= @current_version
88
+ when "minor"
89
+ # Allow minor + patch updates within same major
90
+ same_major?(available) && available >= @current_version
91
+ when "major"
92
+ # Allow any update (including major version bumps)
93
+ available >= @current_version
94
+ else
95
+ false
96
+ end
97
+ end
98
+
99
+ # Generate human-readable reason for policy decision
100
+ # @param available [Gem::Version] Available version
101
+ # @return [String] Explanation of policy decision
102
+ def policy_reason(available)
103
+ return "No update available" if available <= @current_version
104
+
105
+ case @policy.policy
106
+ when "off"
107
+ "Updates disabled by policy"
108
+ when "exact"
109
+ "Policy requires exact version match"
110
+ when "patch"
111
+ if same_major_minor?(available)
112
+ "Patch update allowed by policy"
113
+ else
114
+ "Minor or major version change blocked by patch policy"
115
+ end
116
+ when "minor"
117
+ if same_major?(available)
118
+ "Minor/patch update allowed by policy"
119
+ else
120
+ "Major version change blocked by minor policy"
121
+ end
122
+ when "major"
123
+ "Update allowed by major policy"
124
+ else
125
+ "Unknown policy: #{@policy.policy}"
126
+ end
127
+ end
128
+
129
+ # Check if version has same major version
130
+ # @param version [Gem::Version] Version to compare
131
+ # @return [Boolean]
132
+ def same_major?(version)
133
+ version.segments[0] == @current_version.segments[0]
134
+ end
135
+
136
+ # Check if version has same major.minor version
137
+ # @param version [Gem::Version] Version to compare
138
+ # @return [Boolean]
139
+ def same_major_minor?(version)
140
+ same_major?(version) && version.segments[1] == @current_version.segments[1]
141
+ end
142
+ end
143
+ end
144
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "auto_update/errors"
4
+ require_relative "auto_update/update_policy"
5
+ require_relative "auto_update/update_check"
6
+ require_relative "auto_update/checkpoint"
7
+ require_relative "auto_update/bundler_adapter"
8
+ require_relative "auto_update/rubygems_api_adapter"
9
+ require_relative "auto_update/version_detector"
10
+ require_relative "auto_update/checkpoint_store"
11
+ require_relative "auto_update/update_logger"
12
+ require_relative "auto_update/failure_tracker"
13
+ require_relative "auto_update/coordinator"
14
+
15
+ module Aidp
16
+ # Auto-update functionality for Aidp in devcontainers
17
+ module AutoUpdate
18
+ # Exit code used to signal supervisor to perform update
19
+ UPDATE_EXIT_CODE = 75
20
+
21
+ # Create coordinator from project configuration
22
+ # @param project_dir [String] Project root directory
23
+ # @return [Coordinator]
24
+ def self.coordinator(project_dir: Dir.pwd)
25
+ config = Aidp::Config.load_harness_config(project_dir)
26
+ auto_update_config = config[:auto_update] || config["auto_update"] || {}
27
+
28
+ Coordinator.from_config(auto_update_config, project_dir: project_dir)
29
+ end
30
+
31
+ # Check if auto-update is enabled in configuration
32
+ # @param project_dir [String] Project root directory
33
+ # @return [Boolean]
34
+ def self.enabled?(project_dir: Dir.pwd)
35
+ config = Aidp::Config.load_harness_config(project_dir)
36
+ auto_update_config = config[:auto_update] || config["auto_update"] || {}
37
+
38
+ policy = UpdatePolicy.from_config(auto_update_config)
39
+ !policy.disabled?
40
+ end
41
+
42
+ # Get auto-update policy from configuration
43
+ # @param project_dir [String] Project root directory
44
+ # @return [UpdatePolicy]
45
+ def self.policy(project_dir: Dir.pwd)
46
+ config = Aidp::Config.load_harness_config(project_dir)
47
+ auto_update_config = config[:auto_update] || config["auto_update"] || {}
48
+
49
+ UpdatePolicy.from_config(auto_update_config)
50
+ end
51
+ end
52
+ end
data/lib/aidp/cli.rb CHANGED
@@ -323,6 +323,11 @@ module Aidp
323
323
  opts.separator " show <id> - Show detailed skill information"
324
324
  opts.separator " search <query> - Search skills by keyword"
325
325
  opts.separator " validate [path] - Validate skill file format"
326
+ opts.separator " settings Manage runtime settings"
327
+ opts.separator " auto-update status - Show auto-update configuration"
328
+ opts.separator " auto-update on|off - Enable/disable auto-updates"
329
+ opts.separator " auto-update policy <pol> - Set update policy (off/exact/patch/minor/major)"
330
+ opts.separator " auto-update prerelease - Toggle prerelease updates"
326
331
  opts.separator " harness Manage harness state"
327
332
  opts.separator " config Manage configuration"
328
333
  opts.separator " status - Show harness status"
@@ -376,7 +381,7 @@ module Aidp
376
381
  # Determine if the invocation is a subcommand style call
377
382
  def subcommand?(args)
378
383
  return false if args.nil? || args.empty?
379
- %w[status jobs kb harness providers checkpoint mcp issue config init watch ws work skill].include?(args.first)
384
+ %w[status jobs kb harness providers checkpoint mcp issue config init watch ws work skill settings].include?(args.first)
380
385
  end
381
386
 
382
387
  def run_subcommand(args)
@@ -397,6 +402,7 @@ module Aidp
397
402
  when "ws" then run_ws_command(args)
398
403
  when "work" then run_work_command(args)
399
404
  when "skill" then run_skill_command(args)
405
+ when "settings" then run_settings_command(args)
400
406
  else
401
407
  display_message("Unknown command: #{cmd}", type: :info)
402
408
  return 1
@@ -1854,6 +1860,164 @@ module Aidp
1854
1860
  display_message("Usage: aidp config --interactive [--dry-run]", type: :info)
1855
1861
  end
1856
1862
 
1863
+ def run_settings_command(args)
1864
+ require_relative "auto_update"
1865
+ require "yaml"
1866
+ require "tty-table"
1867
+
1868
+ subcommand = args.shift
1869
+
1870
+ case subcommand
1871
+ when "auto-update"
1872
+ action = args.shift
1873
+
1874
+ case action
1875
+ when "status", nil
1876
+ # Show current auto-update status
1877
+ coordinator = Aidp::AutoUpdate.coordinator(project_dir: Dir.pwd)
1878
+ status = coordinator.status
1879
+
1880
+ display_message("Auto-Update Configuration", type: :highlight)
1881
+ display_message("=" * 60, type: :muted)
1882
+ display_message("Enabled: #{status[:enabled] ? "Yes" : "No"}", type: status[:enabled] ? :success : :muted)
1883
+ display_message("Policy: #{status[:policy]}", type: :info)
1884
+ display_message("Supervisor: #{status[:supervisor]}", type: :info)
1885
+ display_message("Allow Prerelease: #{coordinator.policy.allow_prerelease}", type: :info)
1886
+ display_message("Check Interval: #{coordinator.policy.check_interval_seconds}s", type: :info)
1887
+ display_message("Max Consecutive Failures: #{coordinator.policy.max_consecutive_failures}", type: :info)
1888
+ display_message("", type: :info)
1889
+ display_message("Current Version: #{status[:current_version]}", type: :info)
1890
+ display_message("Latest Available: #{status[:available_version] || "checking..."}", type: :info)
1891
+
1892
+ if status[:update_available]
1893
+ if status[:update_allowed]
1894
+ display_message("Update Available: Yes (allowed by policy)", type: :success)
1895
+ else
1896
+ display_message("Update Available: Yes (blocked by policy: #{status[:policy_reason]})", type: :warning)
1897
+ end
1898
+ else
1899
+ display_message("Update Available: No", type: :muted)
1900
+ end
1901
+
1902
+ display_message("", type: :info)
1903
+ display_message("Failure Tracker:", type: :highlight)
1904
+ failure_status = status[:failure_tracker]
1905
+ display_message("Consecutive Failures: #{failure_status[:failures]}/#{failure_status[:max_failures]}", type: :info)
1906
+ display_message("Last Success: #{failure_status[:last_success] || "never"}", type: :muted)
1907
+
1908
+ if status[:recent_updates]&.any?
1909
+ display_message("", type: :info)
1910
+ display_message("Recent Updates:", type: :highlight)
1911
+ status[:recent_updates].each do |entry|
1912
+ timestamp = entry["timestamp"] || entry[:timestamp]
1913
+ event = entry["event"] || entry[:event]
1914
+ display_message(" #{timestamp} - #{event}", type: :muted)
1915
+ end
1916
+ end
1917
+
1918
+ when "on"
1919
+ # Enable auto-update
1920
+ update_config_value(:auto_update, :enabled, true)
1921
+ display_message("✓ Auto-update enabled", type: :success)
1922
+ display_message("", type: :info)
1923
+ display_message("Make sure to configure a supervisor for watch mode.", type: :muted)
1924
+ display_message("See: docs/SELF_UPDATE.md", type: :muted)
1925
+
1926
+ when "off"
1927
+ # Disable auto-update
1928
+ update_config_value(:auto_update, :enabled, false)
1929
+ display_message("✓ Auto-update disabled", type: :success)
1930
+
1931
+ when "policy"
1932
+ # Set update policy
1933
+ policy = args.shift
1934
+ unless %w[off exact patch minor major].include?(policy)
1935
+ display_message("❌ Invalid policy. Must be: off, exact, patch, minor, major", type: :error)
1936
+ return
1937
+ end
1938
+
1939
+ update_config_value(:auto_update, :policy, policy)
1940
+ display_message("✓ Auto-update policy set to: #{policy}", type: :success)
1941
+ display_message("", type: :info)
1942
+ case policy
1943
+ when "off"
1944
+ display_message("No automatic updates will be performed", type: :muted)
1945
+ when "exact"
1946
+ display_message("Only exact version matches allowed", type: :muted)
1947
+ when "patch"
1948
+ display_message("Patch updates allowed (e.g., 1.2.3 → 1.2.4)", type: :muted)
1949
+ when "minor"
1950
+ display_message("Minor + patch updates allowed (e.g., 1.2.3 → 1.3.0)", type: :muted)
1951
+ when "major"
1952
+ display_message("All updates allowed (e.g., 1.2.3 → 2.0.0)", type: :muted)
1953
+ end
1954
+
1955
+ when "prerelease"
1956
+ # Toggle prerelease
1957
+ current = load_auto_update_config[:allow_prerelease]
1958
+ new_value = !current
1959
+ update_config_value(:auto_update, :allow_prerelease, new_value)
1960
+ display_message("✓ Prerelease updates: #{new_value ? "enabled" : "disabled"}", type: :success)
1961
+
1962
+ else
1963
+ display_message("Usage: aidp settings auto-update <command>", type: :info)
1964
+ display_message("", type: :info)
1965
+ display_message("Commands:", type: :info)
1966
+ display_message(" status Show current configuration", type: :info)
1967
+ display_message(" on Enable auto-updates", type: :info)
1968
+ display_message(" off Disable auto-updates", type: :info)
1969
+ display_message(" policy <policy> Set update policy", type: :info)
1970
+ display_message(" Policies: off, exact, patch, minor, major", type: :muted)
1971
+ display_message(" prerelease Toggle prerelease updates", type: :info)
1972
+ display_message("", type: :info)
1973
+ display_message("Examples:", type: :info)
1974
+ display_message(" aidp settings auto-update status", type: :info)
1975
+ display_message(" aidp settings auto-update on", type: :info)
1976
+ display_message(" aidp settings auto-update policy minor", type: :info)
1977
+ display_message(" aidp settings auto-update prerelease", type: :info)
1978
+ end
1979
+
1980
+ else
1981
+ display_message("Usage: aidp settings <category> <command>", type: :info)
1982
+ display_message("", type: :info)
1983
+ display_message("Categories:", type: :info)
1984
+ display_message(" auto-update Auto-update configuration", type: :info)
1985
+ display_message("", type: :info)
1986
+ display_message("Examples:", type: :info)
1987
+ display_message(" aidp settings auto-update status", type: :info)
1988
+ end
1989
+ end
1990
+
1991
+ # Load current auto_update config
1992
+ def load_auto_update_config
1993
+ config_path = File.join(Dir.pwd, ".aidp", "aidp.yml")
1994
+ return {} unless File.exist?(config_path)
1995
+
1996
+ full_config = YAML.safe_load_file(config_path, permitted_classes: [Date, Time, Symbol], aliases: true)
1997
+ full_config["auto_update"] || full_config[:auto_update] || {}
1998
+ end
1999
+
2000
+ # Update a specific configuration value
2001
+ def update_config_value(section, key, value)
2002
+ config_path = File.join(Dir.pwd, ".aidp", "aidp.yml")
2003
+
2004
+ # Load existing config
2005
+ config = if File.exist?(config_path)
2006
+ YAML.safe_load_file(config_path, permitted_classes: [Date, Time, Symbol], aliases: true) || {}
2007
+ else
2008
+ {}
2009
+ end
2010
+
2011
+ # Ensure section exists
2012
+ config[section.to_s] ||= {}
2013
+
2014
+ # Update value
2015
+ config[section.to_s][key.to_s] = value
2016
+
2017
+ # Write back
2018
+ File.write(config_path, YAML.dump(config))
2019
+ end
2020
+
1857
2021
  def run_skill_command(args)
1858
2022
  require_relative "skills"
1859
2023
  require "tty-table"
@@ -1083,6 +1083,56 @@ module Aidp
1083
1083
  max: 365
1084
1084
  }
1085
1085
  }
1086
+ },
1087
+ auto_update: {
1088
+ type: :hash,
1089
+ required: false,
1090
+ default: {
1091
+ enabled: false,
1092
+ policy: "off",
1093
+ allow_prerelease: false,
1094
+ check_interval_seconds: 3600,
1095
+ supervisor: "none",
1096
+ max_consecutive_failures: 3
1097
+ },
1098
+ properties: {
1099
+ enabled: {
1100
+ type: :boolean,
1101
+ required: false,
1102
+ default: false
1103
+ },
1104
+ policy: {
1105
+ type: :string,
1106
+ required: false,
1107
+ default: "off",
1108
+ enum: ["off", "exact", "patch", "minor", "major"]
1109
+ },
1110
+ allow_prerelease: {
1111
+ type: :boolean,
1112
+ required: false,
1113
+ default: false
1114
+ },
1115
+ check_interval_seconds: {
1116
+ type: :integer,
1117
+ required: false,
1118
+ default: 3600,
1119
+ min: 300,
1120
+ max: 86400
1121
+ },
1122
+ supervisor: {
1123
+ type: :string,
1124
+ required: false,
1125
+ default: "none",
1126
+ enum: ["none", "supervisord", "s6", "runit"]
1127
+ },
1128
+ max_consecutive_failures: {
1129
+ type: :integer,
1130
+ required: false,
1131
+ default: 3,
1132
+ min: 1,
1133
+ max: 10
1134
+ }
1135
+ }
1086
1136
  }
1087
1137
  }.freeze
1088
1138
 
@@ -6,6 +6,7 @@ require_relative "../providers/cursor"
6
6
  require_relative "../providers/anthropic"
7
7
  require_relative "../providers/gemini"
8
8
  require_relative "../providers/opencode"
9
+ require_relative "../providers/kilocode"
9
10
  require_relative "../providers/github_copilot"
10
11
  require_relative "../providers/codex"
11
12
 
@@ -19,6 +20,7 @@ module Aidp
19
20
  "claude" => Aidp::Providers::Anthropic,
20
21
  "gemini" => Aidp::Providers::Gemini,
21
22
  "opencode" => Aidp::Providers::Opencode,
23
+ "kilocode" => Aidp::Providers::Kilocode,
22
24
  "github_copilot" => Aidp::Providers::GithubCopilot,
23
25
  "codex" => Aidp::Providers::Codex
24
26
  }.freeze
@@ -25,8 +25,12 @@ module Aidp
25
25
 
26
26
  # Instance helper for displaying a colored message via TTY::Prompt
27
27
  def display_message(message, type: :info)
28
+ # Ensure message is UTF-8 encoded to handle emoji and special characters
29
+ message_str = message.to_s
30
+ message_str = message_str.force_encoding("UTF-8") if message_str.encoding.name == "ASCII-8BIT"
31
+ message_str = message_str.encode("UTF-8", invalid: :replace, undef: :replace)
28
32
  prompt = message_display_prompt
29
- prompt.say(message, color: COLOR_MAP.fetch(type, :white))
33
+ prompt.say(message_str, color: COLOR_MAP.fetch(type, :white))
30
34
  end
31
35
 
32
36
  # Provide a memoized prompt per including instance (if it defines @prompt)
@@ -41,7 +45,11 @@ module Aidp
41
45
  module ClassMethods
42
46
  # Class-level display helper (uses fresh prompt to respect $stdout changes)
43
47
  def display_message(message, type: :info)
44
- class_message_display_prompt.say(message, color: COLOR_MAP.fetch(type, :white))
48
+ # Ensure message is UTF-8 encoded to handle emoji and special characters
49
+ message_str = message.to_s
50
+ message_str = message_str.force_encoding("UTF-8") if message_str.encoding.name == "ASCII-8BIT"
51
+ message_str = message_str.encode("UTF-8", invalid: :replace, undef: :replace)
52
+ class_message_display_prompt.say(message_str, color: COLOR_MAP.fetch(type, :white))
45
53
  end
46
54
 
47
55
  private
@@ -82,7 +82,7 @@ module Aidp
82
82
  guide_path = File.join(@project_dir, "docs", "LLM_STYLE_GUIDE.md")
83
83
  return nil unless File.exist?(guide_path)
84
84
 
85
- File.read(guide_path)
85
+ File.read(guide_path, encoding: "UTF-8")
86
86
  end
87
87
 
88
88
  # Parse markdown content into fragments
@@ -90,6 +90,8 @@ module Aidp
90
90
  # @param content [String] Markdown content
91
91
  # @return [Array<Fragment>] Parsed fragments
92
92
  def parse_fragments(content)
93
+ # Ensure content is UTF-8 encoded
94
+ content = content.encode("UTF-8", invalid: :replace, undef: :replace) unless content.encoding == Encoding::UTF_8
93
95
  lines = content.lines
94
96
  current_content = []
95
97
  current_heading = nil
@@ -142,6 +142,8 @@ module Aidp
142
142
  Aidp::Providers::Anthropic.new(prompt: prompt)
143
143
  when "gemini"
144
144
  Aidp::Providers::Gemini.new(prompt: prompt)
145
+ when "kilocode"
146
+ Aidp::Providers::Kilocode.new(prompt: prompt)
145
147
  when "github_copilot"
146
148
  Aidp::Providers::GithubCopilot.new(prompt: prompt)
147
149
  when "codex"