aidp 0.11.0 → 0.12.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ffba477d4ca2e297b4fe81d7ecb32ccf37d161df6bcd2dceaa91e94ccbc8ea8f
4
- data.tar.gz: 851fdc47c7a12bd8f6f8304badf60ffdb6855787d0982645d80a309d9caee40b
3
+ metadata.gz: a5c07df32790749c9d60716d1ae3133f0a232cda036e7523b74b8c0725a0b066
4
+ data.tar.gz: b15140f57f3d8cc58dc4b4e79200d3b0ef50152c5f6bcd6f45b030aeca8c863e
5
5
  SHA512:
6
- metadata.gz: ef68650e5a8c55d8b219a00a42ba7fbff2c87bdd5cacf5f450c8bf406c481d6d1e4c00cd7edabbf41b07928bb28c2b5246c54392e67f52ee8381bac1668d9463
7
- data.tar.gz: d2ae0a1bc041b0f8c0cdf1ccb3c328fc7bb1c4ba3cbb0599c983f4937056a8ad1f47e0bcb475a96dd8d4d936cfc6d7452a0333121ed36e29a14708b8e4bd15ec
6
+ metadata.gz: 1e5e5b0f9c209dea98509a29b4004f264cb49706d961cafe42ad2867a40abc6ecd9e3cf279a541e714c914f975ec25f6ed898a7be7549dc6ae1292e369eaa7b9
7
+ data.tar.gz: 5f7a286a65cd9e1ecbdbd167182fa1471e9407171a2fc028cfa144d9204606d8c8e3a7138348c8406133713aa1724749e7abcc0fa6904fca64b65b43355bbfd8
@@ -0,0 +1,205 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "tty-table"
4
+ require_relative "../harness/provider_info"
5
+ require_relative "../harness/configuration"
6
+
7
+ module Aidp
8
+ class CLI
9
+ # Dashboard for viewing MCP servers across all providers
10
+ class McpDashboard
11
+ include Aidp::MessageDisplay
12
+
13
+ def initialize(root_dir = nil)
14
+ @root_dir = root_dir || Dir.pwd
15
+ @configuration = Aidp::Harness::Configuration.new(@root_dir)
16
+ end
17
+
18
+ # Display MCP dashboard showing all servers across all providers
19
+ def display_dashboard(options = {})
20
+ no_color = options[:no_color] || false
21
+
22
+ # Gather MCP server information from all providers
23
+ server_matrix = build_server_matrix
24
+
25
+ # Display summary
26
+ display_message("MCP Server Dashboard", type: :highlight)
27
+ display_message("=" * 80, type: :muted)
28
+ display_message("", type: :info)
29
+
30
+ # Check if there are any MCP-capable providers
31
+ if server_matrix[:providers].empty?
32
+ display_message("No providers with MCP support configured.", type: :info)
33
+ display_message("MCP is supported by providers like: claude", type: :muted)
34
+ display_message("\n" + "=" * 80, type: :muted)
35
+ return
36
+ end
37
+
38
+ # Check if there are any MCP servers configured
39
+ if server_matrix[:servers].empty?
40
+ display_message("No MCP servers configured across any providers.", type: :info)
41
+ display_message("Add MCP servers with: claude mcp add <name> -- <command>", type: :muted)
42
+ display_message("\n" + "=" * 80, type: :muted)
43
+ return
44
+ end
45
+
46
+ # Display the main table
47
+ display_server_table(server_matrix, no_color)
48
+
49
+ # Display eligibility warnings
50
+ display_eligibility_warnings(server_matrix)
51
+
52
+ display_message("\n" + "=" * 80, type: :muted)
53
+ end
54
+
55
+ # Get MCP server availability for a specific task requirement
56
+ def check_task_eligibility(required_servers)
57
+ server_matrix = build_server_matrix
58
+ eligible_providers = []
59
+
60
+ @configuration.configured_providers.each do |provider|
61
+ provider_servers = server_matrix[:provider_servers][provider] || []
62
+ enabled_servers = provider_servers.select { |s| s[:enabled] }.map { |s| s[:name] }
63
+
64
+ # Check if provider has all required servers
65
+ if required_servers.all? { |req| enabled_servers.include?(req) }
66
+ eligible_providers << provider
67
+ end
68
+ end
69
+
70
+ {
71
+ required_servers: required_servers,
72
+ eligible_providers: eligible_providers,
73
+ total_providers: @configuration.configured_providers.size
74
+ }
75
+ end
76
+
77
+ # Display eligibility check for specific servers
78
+ def display_task_eligibility(required_servers)
79
+ result = check_task_eligibility(required_servers)
80
+
81
+ display_message("\nTask Eligibility Check", type: :highlight)
82
+ display_message("Required MCP Servers: #{required_servers.join(", ")}", type: :info)
83
+ display_message("", type: :info)
84
+
85
+ if result[:eligible_providers].any?
86
+ display_message("✓ Eligible Providers (#{result[:eligible_providers].size}/#{result[:total_providers]}):", type: :success)
87
+ result[:eligible_providers].each do |provider|
88
+ display_message(" • #{provider}", type: :success)
89
+ end
90
+ else
91
+ display_message("✗ No providers have all required MCP servers", type: :error)
92
+ display_message(" Consider configuring MCP servers for at least one provider", type: :warning)
93
+ end
94
+ end
95
+
96
+ private
97
+
98
+ def build_server_matrix
99
+ providers = @configuration.configured_providers
100
+ all_servers = {} # server_name => {providers: {provider_name => server_info}}
101
+ provider_servers = {} # provider_name => [server_info]
102
+
103
+ providers.each do |provider|
104
+ provider_info = Aidp::Harness::ProviderInfo.new(provider, @root_dir)
105
+ info = provider_info.get_info
106
+
107
+ next unless info[:mcp_support]
108
+
109
+ servers = info[:mcp_servers] || []
110
+ provider_servers[provider] = servers
111
+
112
+ servers.each do |server|
113
+ server_name = server[:name]
114
+ all_servers[server_name] ||= {providers: {}}
115
+ all_servers[server_name][:providers][provider] = server
116
+ end
117
+ end
118
+
119
+ {
120
+ servers: all_servers,
121
+ provider_servers: provider_servers,
122
+ providers: providers.select { |p| provider_servers.key?(p) }
123
+ }
124
+ end
125
+
126
+ def display_server_table(matrix, no_color)
127
+ # Check if we have any providers with MCP support
128
+ if matrix[:providers].empty?
129
+ display_message("No providers with MCP support configured.", type: :info)
130
+ return
131
+ end
132
+
133
+ # Build table rows
134
+ headers = ["MCP Server"] + matrix[:providers].map { |p| normalize_provider_name(p) }
135
+ rows = []
136
+
137
+ matrix[:servers].keys.sort.each do |server_name|
138
+ row = [server_name]
139
+
140
+ matrix[:providers].each do |provider|
141
+ server = matrix[:servers][server_name][:providers][provider]
142
+ cell = if server
143
+ format_server_status(server, no_color)
144
+ else
145
+ (no_color || !$stdout.tty?) ? "-" : "\e[90m-\e[0m"
146
+ end
147
+ row << cell
148
+ end
149
+
150
+ rows << row
151
+ end
152
+
153
+ # Create and display table
154
+ table = TTY::Table.new(headers, rows)
155
+ display_message(table.render(:basic), type: :info)
156
+ display_message("", type: :info)
157
+
158
+ # Legend
159
+ display_legend(no_color)
160
+ end
161
+
162
+ def format_server_status(server, no_color)
163
+ if no_color || !$stdout.tty?
164
+ server[:enabled] ? "✓" : "✗"
165
+ elsif server[:enabled]
166
+ "\e[32m✓\e[0m" # Green checkmark
167
+ else
168
+ "\e[31m✗\e[0m" # Red X
169
+ end
170
+ end
171
+
172
+ def display_legend(no_color)
173
+ if no_color || !$stdout.tty?
174
+ display_message("Legend: ✓ = Enabled ✗ = Error/Disabled - = Not configured", type: :muted)
175
+ else
176
+ display_message("Legend: \e[32m✓\e[0m = Enabled \e[31m✗\e[0m = Error/Disabled \e[90m-\e[0m = Not configured", type: :muted)
177
+ end
178
+ end
179
+
180
+ def display_eligibility_warnings(matrix)
181
+ # Find servers that are only configured on some providers
182
+ partially_configured = matrix[:servers].select do |_name, info|
183
+ configured_count = info[:providers].size
184
+ configured_count > 0 && configured_count < matrix[:providers].size
185
+ end
186
+
187
+ return if partially_configured.empty?
188
+
189
+ display_message("\n⚠ Eligibility Warnings:", type: :warning)
190
+ partially_configured.each do |server_name, info|
191
+ missing_providers = matrix[:providers] - info[:providers].keys
192
+ if missing_providers.any?
193
+ display_message(" • '#{server_name}' not configured on: #{missing_providers.join(", ")}", type: :warning)
194
+ display_message(" These providers won't be eligible for tasks requiring this MCP server", type: :muted)
195
+ end
196
+ end
197
+ end
198
+
199
+ def normalize_provider_name(name)
200
+ return "claude" if name == "anthropic"
201
+ name
202
+ end
203
+ end
204
+ end
205
+ end
data/lib/aidp/cli.rb CHANGED
@@ -183,7 +183,7 @@ module Aidp
183
183
 
184
184
  # Initialize the enhanced TUI
185
185
  tui = Aidp::Harness::UI::EnhancedTUI.new
186
- workflow_selector = Aidp::Harness::UI::EnhancedWorkflowSelector.new(tui)
186
+ workflow_selector = Aidp::Harness::UI::EnhancedWorkflowSelector.new(tui, project_dir: Dir.pwd)
187
187
 
188
188
  # Start TUI display loop
189
189
  tui.start_display_loop
@@ -245,6 +245,11 @@ module Aidp
245
245
  opts.separator " metrics - Show detailed metrics"
246
246
  opts.separator " clear [--force] - Clear checkpoint data"
247
247
  opts.separator " providers Show provider health dashboard"
248
+ opts.separator " info <name> - Show detailed provider information"
249
+ opts.separator " refresh [name] - Refresh provider capabilities info"
250
+ opts.separator " mcp MCP server dashboard and management"
251
+ opts.separator " dashboard - Show all MCP servers across providers"
252
+ opts.separator " check <servers...> - Check provider eligibility for servers"
248
253
  opts.separator " harness Manage harness state"
249
254
  opts.separator " status - Show harness status"
250
255
  opts.separator " reset - Reset harness state"
@@ -274,6 +279,10 @@ module Aidp
274
279
  opts.separator ""
275
280
  opts.separator " # Other commands"
276
281
  opts.separator " aidp providers # Check provider health"
282
+ opts.separator " aidp providers info claude # Show detailed provider info"
283
+ opts.separator " aidp providers refresh # Refresh all provider info"
284
+ opts.separator " aidp mcp # Show MCP server dashboard"
285
+ opts.separator " aidp mcp check dash-api filesystem # Check provider eligibility"
277
286
  opts.separator " aidp checkpoint history 20 # Show last 20 checkpoints"
278
287
  opts.separator ""
279
288
  opts.separator "For more information, visit: https://github.com/viamin/aidp"
@@ -287,7 +296,7 @@ module Aidp
287
296
  # Determine if the invocation is a subcommand style call
288
297
  def subcommand?(args)
289
298
  return false if args.nil? || args.empty?
290
- %w[status jobs kb harness execute analyze providers checkpoint].include?(args.first)
299
+ %w[status jobs kb harness execute analyze providers checkpoint mcp].include?(args.first)
291
300
  end
292
301
 
293
302
  def run_subcommand(args)
@@ -301,6 +310,7 @@ module Aidp
301
310
  when "analyze" then run_execute_command(args, mode: :analyze) # symmetry
302
311
  when "providers" then run_providers_command(args)
303
312
  when "checkpoint" then run_checkpoint_command(args)
313
+ when "mcp" then run_mcp_command(args)
304
314
  else
305
315
  display_message("Unknown command: #{cmd}", type: :info)
306
316
  return 1
@@ -585,6 +595,19 @@ module Aidp
585
595
  end
586
596
 
587
597
  def run_providers_command(args)
598
+ subcommand = args.first if args.first && !args.first.start_with?("--")
599
+
600
+ case subcommand
601
+ when "info"
602
+ args.shift # Remove 'info'
603
+ run_providers_info_command(args)
604
+ return
605
+ when "refresh"
606
+ args.shift # Remove 'refresh'
607
+ run_providers_refresh_command(args)
608
+ return
609
+ end
610
+
588
611
  # Accept flags directly on `aidp providers` now (health is implicit)
589
612
  no_color = false
590
613
  args.reject! do |a|
@@ -660,6 +683,180 @@ module Aidp
660
683
  display_message("Failed to display provider health: #{e.message}", type: :error)
661
684
  end
662
685
 
686
+ def run_providers_info_command(args)
687
+ require_relative "harness/provider_info"
688
+
689
+ provider_name = args.shift
690
+ unless provider_name
691
+ display_message("Usage: aidp providers info <provider_name>", type: :info)
692
+ display_message("Example: aidp providers info claude", type: :info)
693
+ return
694
+ end
695
+
696
+ force_refresh = args.include?("--refresh")
697
+
698
+ display_message("Provider Information: #{provider_name}", type: :highlight)
699
+ display_message("=" * 60, type: :muted)
700
+
701
+ provider_info = Aidp::Harness::ProviderInfo.new(provider_name, Dir.pwd)
702
+ info = provider_info.get_info(force_refresh: force_refresh)
703
+
704
+ if info.nil?
705
+ display_message("No information available for provider: #{provider_name}", type: :error)
706
+ return
707
+ end
708
+
709
+ # Display basic info
710
+ display_message("Last Checked: #{info[:last_checked]}", type: :info)
711
+ display_message("CLI Available: #{info[:cli_available] ? "Yes" : "No"}", type: info[:cli_available] ? :success : :error)
712
+
713
+ # Display authentication
714
+ if info[:auth_method]
715
+ display_message("\nAuthentication Method: #{info[:auth_method]}", type: :info)
716
+ end
717
+
718
+ # Display MCP support
719
+ display_message("\nMCP Support: #{info[:mcp_support] ? "Yes" : "No"}", type: info[:mcp_support] ? :success : :info)
720
+
721
+ # Display MCP servers if available
722
+ if info[:mcp_servers]&.any?
723
+ display_message("\nMCP Servers: (#{info[:mcp_servers].size} configured)", type: :highlight)
724
+ info[:mcp_servers].each do |server|
725
+ status_symbol = server[:enabled] ? "✓" : "○"
726
+ display_message(" #{status_symbol} #{server[:name]} (#{server[:status]})", type: server[:enabled] ? :success : :muted)
727
+ display_message(" #{server[:description]}", type: :muted) if server[:description]
728
+ end
729
+ elsif info[:mcp_support]
730
+ display_message("\nMCP Servers: None configured", type: :muted)
731
+ end
732
+
733
+ # Display permission modes
734
+ if info[:permission_modes]&.any?
735
+ display_message("\nPermission Modes:", type: :highlight)
736
+ info[:permission_modes].each do |mode|
737
+ display_message(" - #{mode}", type: :info)
738
+ end
739
+ end
740
+
741
+ # Display capabilities
742
+ if info[:capabilities]&.any?
743
+ display_message("\nCapabilities:", type: :highlight)
744
+ info[:capabilities].each do |cap, value|
745
+ next unless value
746
+
747
+ display_message(" ✓ #{cap.to_s.split("_").map(&:capitalize).join(" ")}", type: :success)
748
+ end
749
+ end
750
+
751
+ # Display notable flags
752
+ if info[:flags]&.any?
753
+ display_message("\nNotable Flags: (#{info[:flags].size} total)", type: :highlight)
754
+ # Show first 10 flags
755
+ info[:flags].take(10).each do |name, flag_info|
756
+ display_message(" #{flag_info[:flag]}", type: :info)
757
+ display_message(" #{flag_info[:description][0..80]}...", type: :muted) if flag_info[:description]
758
+ end
759
+
760
+ if info[:flags].size > 10
761
+ display_message("\n ... and #{info[:flags].size - 10} more flags", type: :muted)
762
+ display_message(" Run '#{get_binary_name(provider_name)} --help' for full details", type: :muted)
763
+ end
764
+ end
765
+
766
+ display_message("\n" + "=" * 60, type: :muted)
767
+ display_message("Tip: Use --refresh to update this information", type: :muted)
768
+ end
769
+
770
+ def run_providers_refresh_command(args)
771
+ require_relative "harness/provider_info"
772
+ require "tty-spinner"
773
+
774
+ provider_name = args.shift
775
+ configuration = Aidp::Harness::Configuration.new(Dir.pwd)
776
+ providers_to_refresh = if provider_name
777
+ [provider_name]
778
+ else
779
+ configuration.configured_providers
780
+ end
781
+
782
+ display_message("Refreshing provider information...", type: :info)
783
+ display_message("", type: :info)
784
+
785
+ providers_to_refresh.each do |prov|
786
+ spinner = TTY::Spinner.new("[:spinner] #{prov}...", format: :dots)
787
+ spinner.auto_spin
788
+
789
+ provider_info = Aidp::Harness::ProviderInfo.new(prov, Dir.pwd)
790
+ info = provider_info.gather_info
791
+
792
+ if info[:cli_available]
793
+ spinner.success("(available)")
794
+ else
795
+ spinner.error("(unavailable)")
796
+ end
797
+ end
798
+
799
+ display_message("\n✓ Provider information refreshed", type: :success)
800
+ display_message("Use 'aidp providers info <name>' to view details", type: :muted)
801
+ end
802
+
803
+ def run_mcp_command(args)
804
+ require_relative "cli/mcp_dashboard"
805
+
806
+ subcommand = args.shift
807
+
808
+ dashboard = Aidp::CLI::McpDashboard.new(Dir.pwd)
809
+
810
+ case subcommand
811
+ when "dashboard", "list", nil
812
+ # Extract flags
813
+ no_color = args.include?("--no-color")
814
+ dashboard.display_dashboard(no_color: no_color)
815
+
816
+ when "check"
817
+ # Check eligibility for specific servers
818
+ required_servers = args
819
+ if required_servers.empty?
820
+ display_message("Usage: aidp mcp check <server1> [server2] ...", type: :info)
821
+ display_message("Example: aidp mcp check filesystem brave-search", type: :info)
822
+ return
823
+ end
824
+
825
+ dashboard.display_task_eligibility(required_servers)
826
+
827
+ else
828
+ display_message("Usage: aidp mcp <command>", type: :info)
829
+ display_message("", type: :info)
830
+ display_message("Commands:", type: :info)
831
+ display_message(" dashboard, list Show MCP servers across all providers (default)", type: :info)
832
+ display_message(" check <servers...> Check which providers have required MCP servers", type: :info)
833
+ display_message("", type: :info)
834
+ display_message("Examples:", type: :info)
835
+ display_message(" aidp mcp # Show dashboard", type: :info)
836
+ display_message(" aidp mcp dashboard --no-color # Show without colors", type: :info)
837
+ display_message(" aidp mcp check filesystem dash-api # Check provider eligibility", type: :info)
838
+ end
839
+ end
840
+
841
+ def get_binary_name(provider_name)
842
+ case provider_name
843
+ when "claude", "anthropic"
844
+ "claude"
845
+ when "cursor"
846
+ "cursor"
847
+ when "gemini"
848
+ "gemini"
849
+ when "codex"
850
+ "codex"
851
+ when "github_copilot"
852
+ "gh"
853
+ when "opencode"
854
+ "opencode"
855
+ else
856
+ provider_name
857
+ end
858
+ end
859
+
663
860
  def extract_mode_option(args)
664
861
  mode = nil
665
862
  args.each do |arg|
@@ -677,17 +874,20 @@ module Aidp
677
874
 
678
875
  def select_mode_interactive(tui)
679
876
  mode_options = [
877
+ "🤖 Guided Workflow (Copilot) - AI helps you choose the right workflow",
680
878
  "🔬 Analyze Mode - Analyze your codebase for insights and recommendations",
681
879
  "🏗️ Execute Mode - Build new features with guided development workflow"
682
880
  ]
683
881
  selected = tui.single_select("Welcome to AI Dev Pipeline! Choose your mode", mode_options, default: 1)
684
882
  # Announce mode explicitly in headless contexts (handled internally otherwise)
685
883
  if (defined?(RSpec) || ENV["RSPEC_RUNNING"]) && tui.respond_to?(:announce_mode)
686
- tui.announce_mode(:analyze) if selected == mode_options[0]
687
- tui.announce_mode(:execute) if selected == mode_options[1]
884
+ tui.announce_mode(:guided) if selected == mode_options[0]
885
+ tui.announce_mode(:analyze) if selected == mode_options[1]
886
+ tui.announce_mode(:execute) if selected == mode_options[2]
688
887
  end
689
- return :analyze if selected == mode_options[0]
690
- return :execute if selected == mode_options[1]
888
+ return :guided if selected == mode_options[0]
889
+ return :analyze if selected == mode_options[1]
890
+ return :execute if selected == mode_options[2]
691
891
  :analyze
692
892
  end
693
893
 
@@ -0,0 +1,366 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require "yaml"
5
+ require "fileutils"
6
+
7
+ module Aidp
8
+ module Harness
9
+ # Stores detailed information about AI providers gathered from their CLI tools
10
+ class ProviderInfo
11
+ attr_reader :provider_name, :info_file_path
12
+
13
+ def initialize(provider_name, root_dir = nil)
14
+ @provider_name = provider_name
15
+ @root_dir = root_dir || Dir.pwd
16
+ @info_file_path = File.join(@root_dir, ".aidp", "providers", "#{provider_name}_info.yml")
17
+ ensure_directory_exists
18
+ end
19
+
20
+ # Gather information about the provider by introspecting its CLI
21
+ def gather_info
22
+ info = {
23
+ provider: @provider_name,
24
+ last_checked: Time.now.iso8601,
25
+ cli_available: false,
26
+ help_output: nil,
27
+ capabilities: {},
28
+ permission_modes: [],
29
+ mcp_support: false,
30
+ mcp_servers: [],
31
+ auth_method: nil,
32
+ flags: {}
33
+ }
34
+
35
+ # Try to get help output from the provider CLI
36
+ help_output = fetch_help_output
37
+ if help_output
38
+ info[:cli_available] = true
39
+ info[:help_output] = help_output
40
+ info.merge!(parse_help_output(help_output))
41
+ end
42
+
43
+ # Try to get MCP server list if supported
44
+ if info[:mcp_support]
45
+ mcp_servers = fetch_mcp_servers
46
+ info[:mcp_servers] = mcp_servers if mcp_servers
47
+ end
48
+
49
+ save_info(info)
50
+ info
51
+ end
52
+
53
+ # Load stored provider info
54
+ def load_info
55
+ return nil unless File.exist?(@info_file_path)
56
+
57
+ YAML.safe_load_file(@info_file_path, permitted_classes: [Time, Symbol])
58
+ rescue => e
59
+ warn "Failed to load provider info for #{@provider_name}: #{e.message}"
60
+ nil
61
+ end
62
+
63
+ # Get provider info, refreshing if needed
64
+ def get_info(force_refresh: false, max_age: 86400)
65
+ existing_info = load_info
66
+
67
+ # Refresh if forced, missing, or stale
68
+ if force_refresh || existing_info.nil? || info_stale?(existing_info, max_age)
69
+ gather_info
70
+ else
71
+ existing_info
72
+ end
73
+ end
74
+
75
+ # Check if provider supports MCP servers
76
+ def supports_mcp?
77
+ info = load_info
78
+ return false unless info
79
+
80
+ info[:mcp_support] == true
81
+ end
82
+
83
+ # Get permission modes available
84
+ def permission_modes
85
+ info = load_info
86
+ return [] unless info
87
+
88
+ info[:permission_modes] || []
89
+ end
90
+
91
+ # Get authentication method
92
+ def auth_method
93
+ info = load_info
94
+ return nil unless info
95
+
96
+ info[:auth_method]
97
+ end
98
+
99
+ # Get available flags/options
100
+ def available_flags
101
+ info = load_info
102
+ return {} unless info
103
+
104
+ info[:flags] || {}
105
+ end
106
+
107
+ # Get configured MCP servers
108
+ def mcp_servers
109
+ info = load_info
110
+ return [] unless info
111
+
112
+ info[:mcp_servers] || []
113
+ end
114
+
115
+ # Check if provider has MCP servers configured
116
+ def has_mcp_servers?
117
+ mcp_servers.any?
118
+ end
119
+
120
+ private
121
+
122
+ def ensure_directory_exists
123
+ dir = File.dirname(@info_file_path)
124
+ FileUtils.mkdir_p(dir) unless Dir.exist?(dir)
125
+ end
126
+
127
+ def save_info(info)
128
+ File.write(@info_file_path, YAML.dump(info))
129
+ end
130
+
131
+ def info_stale?(info, max_age)
132
+ return true unless info[:last_checked]
133
+
134
+ last_checked = Time.parse(info[:last_checked].to_s)
135
+ (Time.now - last_checked) > max_age
136
+ rescue
137
+ true
138
+ end
139
+
140
+ def fetch_help_output
141
+ execute_provider_command("--help")
142
+ end
143
+
144
+ def fetch_mcp_servers
145
+ binary = get_binary_name
146
+ return nil unless binary
147
+
148
+ # Try different MCP list commands based on provider
149
+ mcp_output = case @provider_name
150
+ when "claude", "anthropic"
151
+ execute_provider_command("mcp", "list")
152
+ end
153
+
154
+ return nil unless mcp_output
155
+
156
+ parse_mcp_servers(mcp_output)
157
+ end
158
+
159
+ def execute_provider_command(*args)
160
+ binary = get_binary_name
161
+ return nil unless binary
162
+
163
+ # Try to find the binary
164
+ path = begin
165
+ Aidp::Util.which(binary)
166
+ rescue
167
+ nil
168
+ end
169
+ return nil unless path
170
+
171
+ # Execute command with timeout
172
+ begin
173
+ r, w = IO.pipe
174
+ pid = Process.spawn(binary, *args, out: w, err: w)
175
+ w.close
176
+
177
+ # Wait with timeout
178
+ deadline = Time.now + 5
179
+ status = nil
180
+ while Time.now < deadline
181
+ pid_done, status = Process.waitpid2(pid, Process::WNOHANG)
182
+ break if pid_done
183
+ sleep 0.05
184
+ end
185
+
186
+ # Kill if timed out
187
+ unless status
188
+ begin
189
+ Process.kill("TERM", pid)
190
+ sleep 0.1
191
+ Process.kill("KILL", pid)
192
+ rescue
193
+ nil
194
+ end
195
+ return nil
196
+ end
197
+
198
+ output = r.read
199
+ r.close
200
+ output
201
+ rescue
202
+ nil
203
+ end
204
+ end
205
+
206
+ def parse_mcp_servers(output)
207
+ servers = []
208
+ return servers unless output
209
+
210
+ # Parse MCP server list output
211
+ # Claude format (as of 2025):
212
+ # dash-api: uvx --from git+https://... - ✓ Connected
213
+ # or
214
+ # server-name: command - ✗ Error message
215
+ #
216
+ # Legacy format:
217
+ # Name Status Description
218
+ # filesystem enabled File system access
219
+
220
+ lines = output.lines
221
+
222
+ # Skip header lines
223
+ lines.reject! { |line| /checking mcp server health/i.match?(line) }
224
+
225
+ lines.each do |line|
226
+ line = line.strip
227
+ next if line.empty?
228
+
229
+ # Try to parse new Claude format: "name: command - ✓ Connected"
230
+ if line =~ /^([^:]+):\s*(.+?)\s*-\s*(✓|✗)\s*(.+)$/
231
+ name = Regexp.last_match(1).strip
232
+ command = Regexp.last_match(2).strip
233
+ status_symbol = Regexp.last_match(3)
234
+ status_text = Regexp.last_match(4).strip
235
+
236
+ servers << {
237
+ name: name,
238
+ status: (status_symbol == "✓") ? "connected" : "error",
239
+ description: command,
240
+ enabled: status_symbol == "✓",
241
+ error: (status_symbol == "✗") ? status_text : nil
242
+ }
243
+ next
244
+ end
245
+
246
+ # Try to parse legacy table format
247
+ # Skip header line
248
+ next if /Name.*Status/i.match?(line)
249
+ next if /^[-=]+$/.match?(line) # Skip separator lines
250
+
251
+ # Parse table format: columns separated by multiple spaces
252
+ parts = line.split(/\s{2,}/)
253
+ next if parts.size < 2
254
+
255
+ name = parts[0]&.strip
256
+ status = parts[1]&.strip
257
+ description = parts[2..]&.join(" ")&.strip
258
+
259
+ next unless name && !name.empty?
260
+
261
+ servers << {
262
+ name: name,
263
+ status: status || "unknown",
264
+ description: description,
265
+ enabled: status&.downcase == "enabled" || status&.downcase == "connected"
266
+ }
267
+ end
268
+
269
+ servers
270
+ end
271
+
272
+ def get_binary_name
273
+ case @provider_name
274
+ when "claude", "anthropic"
275
+ "claude"
276
+ when "cursor"
277
+ "cursor"
278
+ when "gemini"
279
+ "gemini"
280
+ when "codex"
281
+ "codex"
282
+ when "github_copilot"
283
+ "gh"
284
+ when "opencode"
285
+ "opencode"
286
+ else
287
+ @provider_name
288
+ end
289
+ end
290
+
291
+ def parse_help_output(help_text)
292
+ parsed = {
293
+ capabilities: {},
294
+ permission_modes: [],
295
+ mcp_support: false,
296
+ auth_method: nil,
297
+ flags: {}
298
+ }
299
+
300
+ # Check for MCP support
301
+ parsed[:mcp_support] = !!(help_text =~ /mcp|MCP|Model Context Protocol/i)
302
+
303
+ # Extract permission modes
304
+ if help_text =~ /--permission-mode\s+<mode>\s+.*?\(choices:\s*([^)]+)\)/m
305
+ modes = Regexp.last_match(1).split(",").map(&:strip).map { |m| m.gsub(/["']/, "") }
306
+ parsed[:permission_modes] = modes
307
+ end
308
+
309
+ # Check for dangerous skip permissions
310
+ parsed[:capabilities][:bypass_permissions] = !!(help_text =~ /--dangerously-skip-permissions/)
311
+
312
+ # Check for API key / subscription patterns
313
+ if /--api-key|API_KEY|setup-token|subscription/i.match?(help_text)
314
+ parsed[:auth_method] = if /setup-token|subscription/i.match?(help_text)
315
+ "subscription"
316
+ else
317
+ "api_key"
318
+ end
319
+ end
320
+
321
+ # Extract model configuration
322
+ parsed[:capabilities][:model_selection] = !!(help_text =~ /--model\s+<model>/)
323
+
324
+ # Extract MCP configuration
325
+ parsed[:capabilities][:mcp_config] = !!(help_text =~ /--mcp-config/)
326
+
327
+ # Extract allowed/disallowed tools
328
+ parsed[:capabilities][:tool_restrictions] = !!(help_text =~ /--allowed-tools|--disallowed-tools/)
329
+
330
+ # Extract session management
331
+ parsed[:capabilities][:session_management] = !!(help_text =~ /--continue|--resume|--fork-session/)
332
+
333
+ # Extract output formats
334
+ if help_text =~ /--output-format\s+.*?\(choices:\s*([^)]+)\)/m
335
+ formats = Regexp.last_match(1).split(",").map(&:strip).map { |f| f.gsub(/["']/, "") }
336
+ parsed[:capabilities][:output_formats] = formats
337
+ end
338
+
339
+ # Extract notable flags
340
+ extract_flags(help_text, parsed[:flags])
341
+
342
+ parsed
343
+ end
344
+
345
+ def extract_flags(help_text, flags_hash)
346
+ # Extract all flags with their descriptions
347
+ help_text.scan(/^\s+(--[\w-]+(?:\s+<\w+>)?)\s+(.+?)(?=^\s+(?:--|\w|$))/m).each do |flag, desc|
348
+ flag_name = flag.split.first.gsub(/^--/, "")
349
+ flags_hash[flag_name] = {
350
+ flag: flag.strip,
351
+ description: desc.strip.gsub(/\s+/, " ")
352
+ }
353
+ end
354
+
355
+ # Also capture short flags
356
+ help_text.scan(/^\s+(-\w),\s+(--[\w-]+(?:\s+<\w+>)?)\s+(.+?)(?=^\s+(?:--|-\w|$))/m).each do |short, long, desc|
357
+ flag_name = long.split.first.gsub(/^--/, "")
358
+ flags_hash[flag_name] ||= {}
359
+ flags_hash[flag_name][:short] = short
360
+ flags_hash[flag_name][:flag] = long.strip
361
+ flags_hash[flag_name][:description] = desc.strip.gsub(/\s+/, " ")
362
+ end
363
+ end
364
+ end
365
+ end
366
+ end
@@ -2,6 +2,7 @@
2
2
 
3
3
  require_relative "enhanced_tui"
4
4
  require_relative "../../workflows/selector"
5
+ require_relative "../../workflows/guided_agent"
5
6
 
6
7
  module Aidp
7
8
  module Harness
@@ -10,10 +11,11 @@ module Aidp
10
11
  class EnhancedWorkflowSelector
11
12
  class WorkflowError < StandardError; end
12
13
 
13
- def initialize(tui = nil)
14
+ def initialize(tui = nil, project_dir: Dir.pwd)
14
15
  @tui = tui || EnhancedTUI.new
15
16
  @user_input = {}
16
17
  @workflow_selector = Aidp::Workflows::Selector.new
18
+ @project_dir = project_dir
17
19
  end
18
20
 
19
21
  def select_workflow(harness_mode: false, mode: :analyze)
@@ -28,6 +30,8 @@ module Aidp
28
30
 
29
31
  def select_workflow_interactive(mode)
30
32
  case mode
33
+ when :guided
34
+ select_guided_workflow
31
35
  when :analyze
32
36
  select_analyze_workflow_interactive
33
37
  when :execute
@@ -243,6 +247,23 @@ module Aidp
243
247
  "16_IMPLEMENTATION"
244
248
  ]
245
249
  end
250
+
251
+ def select_guided_workflow
252
+ # Use the guided agent to help user select workflow
253
+ guided_agent = Aidp::Workflows::GuidedAgent.new(@project_dir, prompt: @tui.instance_variable_get(:@prompt))
254
+ result = guided_agent.select_workflow
255
+
256
+ # Store user input for later use
257
+ @user_input = result[:user_input]
258
+
259
+ # Return in the expected format
260
+ {
261
+ workflow_type: result[:workflow_type],
262
+ steps: result[:steps],
263
+ user_input: @user_input,
264
+ workflow: result[:workflow]
265
+ }
266
+ end
246
267
  end
247
268
  end
248
269
  end
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.11.0"
4
+ VERSION = "0.12.0"
5
5
  end
@@ -0,0 +1,400 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../harness/provider_manager"
4
+ require_relative "../harness/provider_factory"
5
+ require_relative "../harness/configuration"
6
+ require_relative "definitions"
7
+ require_relative "../message_display"
8
+
9
+ module Aidp
10
+ module Workflows
11
+ # Guided workflow agent that uses AI to help users select appropriate workflows
12
+ # Acts as a copilot to match user intent to AIDP capabilities
13
+ class GuidedAgent
14
+ include Aidp::MessageDisplay
15
+
16
+ class ConversationError < StandardError; end
17
+
18
+ def initialize(project_dir, prompt: nil)
19
+ @project_dir = project_dir
20
+ @prompt = prompt || TTY::Prompt.new
21
+ @configuration = Aidp::Harness::Configuration.new(project_dir)
22
+ @provider_manager = Aidp::Harness::ProviderManager.new(@configuration, prompt: @prompt)
23
+ @conversation_history = []
24
+ @user_input = {}
25
+ end
26
+
27
+ # Main entry point for guided workflow selection
28
+ def select_workflow
29
+ display_message("\n🤖 Welcome to AIDP Guided Workflow!", type: :highlight)
30
+ display_message("I'll help you choose the right workflow for your needs.\n", type: :info)
31
+
32
+ # Step 1: Get user's high-level goal
33
+ user_goal = get_user_goal
34
+
35
+ # Step 2: Use AI to analyze intent and recommend workflow
36
+ recommendation = analyze_user_intent(user_goal)
37
+
38
+ # Step 3: Present recommendation and get confirmation
39
+ workflow_selection = present_recommendation(recommendation)
40
+
41
+ # Step 4: Collect any additional required information
42
+ collect_workflow_details(workflow_selection)
43
+
44
+ workflow_selection
45
+ rescue => e
46
+ raise ConversationError, "Failed to guide workflow selection: #{e.message}"
47
+ end
48
+
49
+ private
50
+
51
+ def get_user_goal
52
+ display_message("What would you like to do?", type: :highlight)
53
+ display_message("Examples:", type: :muted)
54
+ display_message(" • Build a new feature for user authentication", type: :muted)
55
+ display_message(" • Understand how this codebase handles payments", type: :muted)
56
+ display_message(" • Improve test coverage in my API layer", type: :muted)
57
+ display_message(" • Create a quick prototype for data export\n", type: :muted)
58
+
59
+ goal = @prompt.ask("Your goal:", required: true)
60
+ @user_input[:original_goal] = goal
61
+ goal
62
+ end
63
+
64
+ def analyze_user_intent(user_goal)
65
+ display_message("\n🔍 Analyzing your request...", type: :info)
66
+
67
+ # Build the system prompt with AIDP capabilities
68
+ system_prompt = build_system_prompt
69
+
70
+ # Build the user prompt
71
+ user_prompt = build_analysis_prompt(user_goal)
72
+
73
+ # Call provider to analyze intent
74
+ response = call_provider_for_analysis(system_prompt, user_prompt)
75
+
76
+ # Parse the response
77
+ parse_recommendation(response)
78
+ end
79
+
80
+ def build_system_prompt
81
+ # Try to load from project dir first, fall back to gem's docs
82
+ capabilities_path = File.join(@project_dir, "docs", "AIDP_CAPABILITIES.md")
83
+ unless File.exist?(capabilities_path)
84
+ # Use the gem's copy
85
+ gem_root = File.expand_path("../../..", __dir__)
86
+ capabilities_path = File.join(gem_root, "docs", "AIDP_CAPABILITIES.md")
87
+ end
88
+
89
+ capabilities_doc = File.read(capabilities_path)
90
+
91
+ <<~PROMPT
92
+ You are an expert AI assistant helping users select the right AIDP workflow for their needs.
93
+
94
+ Your role:
95
+ 1. Understand what the user wants to accomplish
96
+ 2. Match their intent to AIDP's capabilities
97
+ 3. Recommend the most appropriate workflow
98
+ 4. Explain why this workflow fits their needs
99
+ 5. Identify if custom steps or templates are needed
100
+
101
+ AIDP Capabilities Reference:
102
+ #{capabilities_doc}
103
+
104
+ Response Format:
105
+ Provide a JSON response with:
106
+ {
107
+ "mode": "analyze|execute|hybrid",
108
+ "workflow_key": "specific_workflow_key",
109
+ "reasoning": "brief explanation of why this fits",
110
+ "additional_steps": ["any", "custom", "steps", "if", "needed"],
111
+ "questions": ["any", "clarifying", "questions"],
112
+ "confidence": "high|medium|low"
113
+ }
114
+
115
+ Be concise to preserve the user's context window.
116
+ PROMPT
117
+ end
118
+
119
+ def build_analysis_prompt(user_goal)
120
+ <<~PROMPT
121
+ User Goal: #{user_goal}
122
+
123
+ Analyze this goal and recommend the most appropriate AIDP workflow.
124
+ Consider:
125
+ - Is this analysis or development?
126
+ - What level of rigor is needed?
127
+ - Are there any gaps in existing workflows?
128
+
129
+ Provide your recommendation in JSON format.
130
+ PROMPT
131
+ end
132
+
133
+ def call_provider_for_analysis(system_prompt, user_prompt)
134
+ # Get current provider from provider manager
135
+ provider_name = @provider_manager.current_provider
136
+
137
+ unless provider_name
138
+ raise ConversationError, "No provider configured for guided workflow"
139
+ end
140
+
141
+ # Create provider instance using ProviderFactory
142
+ provider_factory = Aidp::Harness::ProviderFactory.new(@configuration)
143
+ provider = provider_factory.create_provider(provider_name, prompt: @prompt)
144
+
145
+ unless provider
146
+ raise ConversationError, "Failed to create provider instance for #{provider_name}"
147
+ end
148
+
149
+ # Make the request - combine system and user prompts
150
+ combined_prompt = "#{system_prompt}\n\n#{user_prompt}"
151
+
152
+ result = provider.send(prompt: combined_prompt)
153
+
154
+ unless result[:status] == :success
155
+ raise ConversationError, "Provider request failed: #{result[:error]}"
156
+ end
157
+
158
+ result[:content]
159
+ end
160
+
161
+ def parse_recommendation(response_text)
162
+ # Extract JSON from response (might be wrapped in markdown code blocks)
163
+ json_match = response_text.match(/```json\s*(\{.*?\})\s*```/m) ||
164
+ response_text.match(/(\{.*\})/m)
165
+
166
+ unless json_match
167
+ raise ConversationError, "Could not parse recommendation from response"
168
+ end
169
+
170
+ JSON.parse(json_match[1], symbolize_names: true)
171
+ rescue JSON::ParserError => e
172
+ raise ConversationError, "Invalid JSON in recommendation: #{e.message}"
173
+ end
174
+
175
+ def present_recommendation(recommendation)
176
+ display_message("\n✨ Recommendation", type: :highlight)
177
+ display_message("─" * 60, type: :muted)
178
+
179
+ mode = recommendation[:mode].to_sym
180
+ workflow_key = recommendation[:workflow_key].to_sym
181
+
182
+ # Get workflow details
183
+ workflow = Definitions.get_workflow(mode, workflow_key)
184
+
185
+ unless workflow
186
+ # Handle custom workflow or error
187
+ return handle_custom_workflow(recommendation)
188
+ end
189
+
190
+ # Display the recommendation
191
+ display_message("Mode: #{mode.to_s.capitalize}", type: :info)
192
+ display_message("Workflow: #{workflow[:name]}", type: :info)
193
+ display_message("\n#{workflow[:description]}", type: :muted)
194
+ display_message("\nReasoning: #{recommendation[:reasoning]}\n", type: :info)
195
+
196
+ # Show what's included
197
+ display_message("This workflow includes:", type: :highlight)
198
+ workflow[:details].each do |detail|
199
+ display_message(" • #{detail}", type: :info)
200
+ end
201
+
202
+ # Handle additional steps if needed
203
+ steps = workflow[:steps]
204
+ if recommendation[:additional_steps]&.any?
205
+ display_message("\nAdditional custom steps recommended:", type: :highlight)
206
+ recommendation[:additional_steps].each do |step|
207
+ display_message(" • #{step}", type: :info)
208
+ end
209
+ steps = workflow[:steps] + recommendation[:additional_steps]
210
+ end
211
+
212
+ # Ask for confirmation
213
+ display_message("")
214
+ confirmed = @prompt.yes?("Does this workflow fit your needs?")
215
+
216
+ if confirmed
217
+ {
218
+ mode: mode,
219
+ workflow_key: workflow_key,
220
+ workflow_type: workflow_key,
221
+ steps: steps,
222
+ user_input: @user_input,
223
+ workflow: workflow
224
+ }
225
+ else
226
+ # Offer alternatives
227
+ offer_alternatives(mode)
228
+ end
229
+ end
230
+
231
+ def handle_custom_workflow(recommendation)
232
+ display_message("\n💡 Custom Workflow Needed", type: :highlight)
233
+ display_message("The AI recommends creating a custom workflow:\n", type: :info)
234
+ display_message(recommendation[:reasoning], type: :muted)
235
+
236
+ if recommendation[:questions]&.any?
237
+ display_message("\nLet me gather some more information:\n", type: :highlight)
238
+ recommendation[:questions].each do |question|
239
+ answer = @prompt.ask(question)
240
+ @user_input[question.downcase.gsub(/\s+/, "_").to_sym] = answer
241
+ end
242
+ end
243
+
244
+ # Let user select from available steps
245
+ mode = recommendation[:mode].to_sym
246
+ display_message("\nLet's build a custom workflow by selecting specific steps:", type: :info)
247
+
248
+ steps = select_custom_steps(mode, recommendation[:additional_steps])
249
+
250
+ {
251
+ mode: mode,
252
+ workflow_key: :custom,
253
+ workflow_type: :custom,
254
+ steps: steps,
255
+ user_input: @user_input,
256
+ workflow: {
257
+ name: "Custom Workflow",
258
+ description: recommendation[:reasoning],
259
+ details: steps
260
+ }
261
+ }
262
+ end
263
+
264
+ def select_custom_steps(mode, suggested_steps = [])
265
+ # Get available steps for the mode
266
+ spec = case mode
267
+ when :analyze
268
+ Aidp::Analyze::Steps::SPEC
269
+ when :execute
270
+ Aidp::Execute::Steps::SPEC
271
+ when :hybrid
272
+ # Combine both
273
+ Aidp::Analyze::Steps::SPEC.merge(Aidp::Execute::Steps::SPEC)
274
+ else
275
+ {}
276
+ end
277
+
278
+ step_choices = spec.map do |step_key, step_spec|
279
+ {
280
+ name: "#{step_key} - #{step_spec["description"]}",
281
+ value: step_key
282
+ }
283
+ end
284
+
285
+ # Pre-select suggested steps
286
+ default_indices = suggested_steps.map do |step|
287
+ step_choices.index { |choice| choice[:value].to_s == step.to_s }
288
+ end.compact
289
+
290
+ selected_steps = @prompt.multi_select(
291
+ "Select steps for your custom workflow:",
292
+ step_choices,
293
+ default: default_indices,
294
+ per_page: 20
295
+ )
296
+
297
+ selected_steps.empty? ? suggested_steps : selected_steps
298
+ end
299
+
300
+ def offer_alternatives(current_mode)
301
+ display_message("\n🔄 Let's find a better fit", type: :highlight)
302
+
303
+ choices = [
304
+ {name: "Try a different #{current_mode} workflow", value: :different_workflow},
305
+ {name: "Switch to a different mode", value: :different_mode},
306
+ {name: "Build a custom workflow", value: :custom},
307
+ {name: "Start over", value: :restart}
308
+ ]
309
+
310
+ choice = @prompt.select("What would you like to do?", choices)
311
+
312
+ case choice
313
+ when :different_workflow
314
+ select_manual_workflow(current_mode)
315
+ when :different_mode
316
+ # Let user pick mode manually then workflow
317
+ new_mode = @prompt.select(
318
+ "Select mode:",
319
+ {
320
+ "🔬 Analyze Mode" => :analyze,
321
+ "🏗️ Execute Mode" => :execute,
322
+ "🔀 Hybrid Mode" => :hybrid
323
+ }
324
+ )
325
+ select_manual_workflow(new_mode)
326
+ when :custom
327
+ select_custom_steps(current_mode)
328
+ when :restart
329
+ select_workflow # Recursive call to start over
330
+ end
331
+ end
332
+
333
+ def select_manual_workflow(mode)
334
+ workflows = Definitions.workflows_for_mode(mode)
335
+
336
+ choices = workflows.map do |key, workflow|
337
+ {
338
+ name: "#{workflow[:icon]} #{workflow[:name]} - #{workflow[:description]}",
339
+ value: key
340
+ }
341
+ end
342
+
343
+ selected_key = @prompt.select("Choose a workflow:", choices, per_page: 15)
344
+ workflow = workflows[selected_key]
345
+
346
+ {
347
+ mode: mode,
348
+ workflow_key: selected_key,
349
+ workflow_type: selected_key,
350
+ steps: workflow[:steps],
351
+ user_input: @user_input,
352
+ workflow: workflow
353
+ }
354
+ end
355
+
356
+ def collect_workflow_details(workflow_selection)
357
+ # Collect additional information based on mode
358
+ case workflow_selection[:mode]
359
+ when :execute
360
+ collect_execute_details
361
+ when :analyze
362
+ # Analyze mode typically doesn't need much user input
363
+ @user_input[:analysis_goal] = @user_input[:original_goal]
364
+ when :hybrid
365
+ collect_execute_details # Hybrid often needs project details
366
+ end
367
+
368
+ # Update the workflow selection with collected input
369
+ workflow_selection[:user_input] = @user_input
370
+ end
371
+
372
+ def collect_execute_details
373
+ return if @user_input[:project_description] # Already collected
374
+
375
+ display_message("\n📝 Project Information", type: :highlight)
376
+ display_message("Let me gather some details for the PRD:\n", type: :info)
377
+
378
+ @user_input[:project_description] = @prompt.ask(
379
+ "Describe what you're building (can reference your original goal):",
380
+ default: @user_input[:original_goal]
381
+ )
382
+
383
+ @user_input[:tech_stack] = @prompt.ask(
384
+ "Tech stack (e.g., Ruby/Rails, Node.js, Python)? [optional]",
385
+ required: false
386
+ )
387
+
388
+ @user_input[:target_users] = @prompt.ask(
389
+ "Who will use this? [optional]",
390
+ required: false
391
+ )
392
+
393
+ @user_input[:success_criteria] = @prompt.ask(
394
+ "How will you measure success? [optional]",
395
+ required: false
396
+ )
397
+ end
398
+ end
399
+ end
400
+ end
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.11.0
4
+ version: 0.12.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bart Agapinan
@@ -250,6 +250,7 @@ files:
250
250
  - lib/aidp/cli/checkpoint_command.rb
251
251
  - lib/aidp/cli/first_run_wizard.rb
252
252
  - lib/aidp/cli/jobs_command.rb
253
+ - lib/aidp/cli/mcp_dashboard.rb
253
254
  - lib/aidp/cli/terminal_io.rb
254
255
  - lib/aidp/config.rb
255
256
  - lib/aidp/core_ext/class_attribute.rb
@@ -274,6 +275,7 @@ files:
274
275
  - lib/aidp/harness/error_handler.rb
275
276
  - lib/aidp/harness/provider_config.rb
276
277
  - lib/aidp/harness/provider_factory.rb
278
+ - lib/aidp/harness/provider_info.rb
277
279
  - lib/aidp/harness/provider_manager.rb
278
280
  - lib/aidp/harness/provider_type_checker.rb
279
281
  - lib/aidp/harness/runner.rb
@@ -324,6 +326,7 @@ files:
324
326
  - lib/aidp/util.rb
325
327
  - lib/aidp/version.rb
326
328
  - lib/aidp/workflows/definitions.rb
329
+ - lib/aidp/workflows/guided_agent.rb
327
330
  - lib/aidp/workflows/selector.rb
328
331
  - templates/COMMON/AGENT_BASE.md
329
332
  - templates/COMMON/CONVENTIONS.md