aidp 0.9.5 → 0.10.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 (42) hide show
  1. checksums.yaml +4 -4
  2. data/lib/aidp/analyze/error_handler.rb +4 -2
  3. data/lib/aidp/{analysis → analyze}/kb_inspector.rb +106 -89
  4. data/lib/aidp/analyze/prioritizer.rb +3 -2
  5. data/lib/aidp/analyze/ruby_maat_integration.rb +20 -3
  6. data/lib/aidp/analyze/runner.rb +27 -9
  7. data/lib/aidp/{analysis → analyze}/seams.rb +1 -1
  8. data/lib/aidp/analyze/steps.rb +7 -7
  9. data/lib/aidp/{analysis → analyze}/tree_sitter_grammar_loader.rb +22 -5
  10. data/lib/aidp/{analysis → analyze}/tree_sitter_scan.rb +32 -15
  11. data/lib/aidp/cli/first_run_wizard.rb +37 -28
  12. data/lib/aidp/cli/jobs_command.rb +37 -18
  13. data/lib/aidp/cli/terminal_io.rb +3 -3
  14. data/lib/aidp/cli.rb +131 -63
  15. data/lib/aidp/execute/runner.rb +27 -9
  16. data/lib/aidp/execute/steps.rb +18 -18
  17. data/lib/aidp/execute/workflow_selector.rb +36 -21
  18. data/lib/aidp/harness/enhanced_runner.rb +3 -3
  19. data/lib/aidp/harness/provider_factory.rb +3 -1
  20. data/lib/aidp/harness/provider_manager.rb +34 -15
  21. data/lib/aidp/harness/runner.rb +24 -5
  22. data/lib/aidp/harness/simple_user_interface.rb +19 -4
  23. data/lib/aidp/harness/status_display.rb +121 -104
  24. data/lib/aidp/harness/ui/enhanced_tui.rb +33 -5
  25. data/lib/aidp/harness/ui/error_handler.rb +3 -2
  26. data/lib/aidp/harness/ui/frame_manager.rb +52 -32
  27. data/lib/aidp/harness/ui/navigation/main_menu.rb +23 -14
  28. data/lib/aidp/harness/ui/progress_display.rb +28 -5
  29. data/lib/aidp/harness/ui/status_widget.rb +17 -8
  30. data/lib/aidp/harness/ui/workflow_controller.rb +25 -9
  31. data/lib/aidp/harness/user_interface.rb +341 -328
  32. data/lib/aidp/provider_manager.rb +10 -6
  33. data/lib/aidp/providers/anthropic.rb +3 -3
  34. data/lib/aidp/providers/base.rb +20 -1
  35. data/lib/aidp/providers/cursor.rb +6 -8
  36. data/lib/aidp/providers/gemini.rb +3 -3
  37. data/lib/aidp/providers/github_copilot.rb +264 -0
  38. data/lib/aidp/providers/opencode.rb +6 -8
  39. data/lib/aidp/version.rb +1 -1
  40. data/lib/aidp.rb +4 -4
  41. metadata +6 -6
  42. data/lib/aidp/analyze/progress_visualizer.rb +0 -314
@@ -5,37 +5,37 @@ module Aidp
5
5
  module Steps
6
6
  SPEC = {
7
7
  "01_REPOSITORY_ANALYSIS" => {
8
- "templates" => ["01_repository_analysis.md"],
8
+ "templates" => ["01_REPOSITORY_ANALYSIS.md"],
9
9
  "description" => "Initial code-maat based repository mining",
10
10
  "outs" => ["docs/analysis/repository_analysis.md"],
11
11
  "gate" => false
12
12
  },
13
13
  "02_ARCHITECTURE_ANALYSIS" => {
14
- "templates" => ["02_architecture_analysis.md"],
14
+ "templates" => ["02_ARCHITECTURE_ANALYSIS.md"],
15
15
  "description" => "Identify architectural patterns, dependencies, and violations",
16
16
  "outs" => ["docs/analysis/architecture_analysis.md"],
17
17
  "gate" => true
18
18
  },
19
19
  "03_TEST_ANALYSIS" => {
20
- "templates" => ["03_test_analysis.md"],
20
+ "templates" => ["03_TEST_ANALYSIS.md"],
21
21
  "description" => "Analyze existing test coverage and identify gaps",
22
22
  "outs" => ["docs/analysis/test_analysis.md"],
23
23
  "gate" => false
24
24
  },
25
25
  "04_FUNCTIONALITY_ANALYSIS" => {
26
- "templates" => ["04_functionality_analysis.md"],
26
+ "templates" => ["04_FUNCTIONALITY_ANALYSIS.md"],
27
27
  "description" => "Map features, identify dead code, analyze complexity",
28
28
  "outs" => ["docs/analysis/functionality_analysis.md"],
29
29
  "gate" => false
30
30
  },
31
31
  "05_DOCUMENTATION_ANALYSIS" => {
32
- "templates" => ["05_documentation_analysis.md"],
32
+ "templates" => ["05_DOCUMENTATION_ANALYSIS.md"],
33
33
  "description" => "Identify missing documentation and generate what's needed",
34
34
  "outs" => ["docs/analysis/documentation_analysis.md"],
35
35
  "gate" => false
36
36
  },
37
37
  "06_STATIC_ANALYSIS" => {
38
- "templates" => ["06_static_analysis.md"],
38
+ "templates" => ["06_STATIC_ANALYSIS.md"],
39
39
  "description" => "Check for existing tools and recommend improvements",
40
40
  "outs" => ["docs/analysis/static_analysis.md"],
41
41
  "gate" => false
@@ -47,7 +47,7 @@ module Aidp
47
47
  "gate" => false
48
48
  },
49
49
  "07_REFACTORING_RECOMMENDATIONS" => {
50
- "templates" => ["07_refactoring_recommendations.md"],
50
+ "templates" => ["07_REFACTORING_RECOMMENDATIONS.md"],
51
51
  "description" => "Provide actionable refactoring guidance",
52
52
  "outs" => ["docs/analysis/refactoring_recommendations.md"],
53
53
  "gate" => true
@@ -2,10 +2,11 @@
2
2
 
3
3
  require "pathname"
4
4
  require "tree_sitter"
5
+ require "tty-prompt"
5
6
  require "fileutils"
6
7
 
7
8
  module Aidp
8
- module Analysis
9
+ module Analyze
9
10
  class TreeSitterGrammarLoader
10
11
  # Default grammar configurations
11
12
  GRAMMAR_CONFIGS = {
@@ -61,10 +62,11 @@ module Aidp
61
62
  }
62
63
  }.freeze
63
64
 
64
- def initialize(project_dir = Dir.pwd)
65
+ def initialize(project_dir = Dir.pwd, prompt: TTY::Prompt.new)
65
66
  @project_dir = project_dir
66
67
  @grammars_dir = File.join(project_dir, ".aidp", "grammars")
67
68
  @loaded_grammars = {}
69
+ @prompt = prompt
68
70
  end
69
71
 
70
72
  # Load grammar for a specific language
@@ -92,7 +94,7 @@ module Aidp
92
94
  grammar_path = File.join(@grammars_dir, language)
93
95
 
94
96
  unless File.exist?(grammar_path)
95
- puts "Installing Tree-sitter grammar for #{language}..."
97
+ display_message("Installing Tree-sitter grammar for #{language}...", type: :info)
96
98
  install_grammar(language, config)
97
99
  end
98
100
  end
@@ -110,7 +112,7 @@ module Aidp
110
112
  require "json"
111
113
  File.write(File.join(grammar_path, "grammar.json"), JSON.generate(config))
112
114
 
113
- puts "Grammar for #{language} marked as available"
115
+ display_message("Grammar for #{language} marked as available", type: :success)
114
116
  end
115
117
 
116
118
  def create_parser(language, config)
@@ -140,7 +142,7 @@ module Aidp
140
142
  real: true
141
143
  }
142
144
  rescue TreeSitter::ParserNotFoundError => e
143
- puts "Warning: Tree-sitter parser not found for #{language}: #{e.message}"
145
+ display_message("Warning: Tree-sitter parser not found for #{language}: #{e.message}", type: :warn)
144
146
  create_mock_parser(language)
145
147
  end
146
148
 
@@ -476,6 +478,21 @@ module Aidp
476
478
 
477
479
  nodes
478
480
  end
481
+
482
+ private
483
+
484
+ # Helper method for consistent message display using TTY::Prompt
485
+ def display_message(message, type: :info)
486
+ color = case type
487
+ when :error then :red
488
+ when :warn then :yellow
489
+ when :success then :green
490
+ when :highlight then :cyan
491
+ else :white
492
+ end
493
+
494
+ @prompt.say(message, color: color)
495
+ end
479
496
  end
480
497
  end
481
498
  end
@@ -4,19 +4,21 @@ require "json"
4
4
  require "fileutils"
5
5
  require "digest"
6
6
  require "etc"
7
+ require "tty-prompt"
7
8
 
8
9
  require_relative "tree_sitter_grammar_loader"
9
10
  require_relative "seams"
10
11
 
11
12
  module Aidp
12
- module Analysis
13
+ module Analyze
13
14
  class TreeSitterScan
14
- def initialize(root: Dir.pwd, kb_dir: ".aidp/kb", langs: %w[ruby], threads: Etc.nprocessors)
15
+ def initialize(root: Dir.pwd, kb_dir: ".aidp/kb", langs: %w[ruby], threads: Etc.nprocessors, prompt: TTY::Prompt.new)
15
16
  @root = File.expand_path(root)
16
17
  @kb_dir = File.expand_path(kb_dir, @root)
17
18
  @langs = Array(langs)
18
19
  @threads = threads
19
- @grammar_loader = TreeSitterGrammarLoader.new(@root)
20
+ @prompt = prompt
21
+ @grammar_loader = TreeSitterGrammarLoader.new(@root, prompt: @prompt)
20
22
 
21
23
  # Data structures to accumulate analysis results
22
24
  @symbols = []
@@ -34,14 +36,14 @@ module Aidp
34
36
  end
35
37
 
36
38
  def run
37
- puts "🔍 Starting Tree-sitter static analysis..."
38
- puts "📁 Root: #{@root}"
39
- puts "🗂️ KB Directory: #{@kb_dir}"
40
- puts "🌐 Languages: #{@langs.join(", ")}"
41
- puts "🧵 Threads: #{@threads}"
39
+ display_message("🔍 Starting Tree-sitter static analysis...", type: :highlight)
40
+ display_message("📁 Root: #{@root}", type: :info)
41
+ display_message("🗂️ KB Directory: #{@kb_dir}", type: :info)
42
+ display_message("🌐 Languages: #{@langs.join(", ")}", type: :info)
43
+ display_message("🧵 Threads: #{@threads}", type: :info)
42
44
 
43
45
  files = discover_files
44
- puts "📄 Found #{files.length} files to analyze"
46
+ display_message("📄 Found #{files.length} files to analyze", type: :info)
45
47
 
46
48
  prepare_kb_dir
47
49
  load_cache
@@ -49,8 +51,8 @@ module Aidp
49
51
  parallel_parse(files)
50
52
  write_kb_files
51
53
 
52
- puts "✅ Tree-sitter analysis complete!"
53
- puts "📊 Generated KB files in #{@kb_dir}"
54
+ display_message("✅ Tree-sitter analysis complete!", type: :success)
55
+ display_message("📊 Generated KB files in #{@kb_dir}", type: :success)
54
56
  end
55
57
 
56
58
  private
@@ -144,14 +146,14 @@ module Aidp
144
146
  end
145
147
 
146
148
  def parallel_parse(files)
147
- puts "🔄 Parsing files in parallel..."
149
+ display_message("🔄 Parsing files in parallel...", type: :info)
148
150
 
149
151
  # Group files by language for efficient processing
150
152
  files_by_lang = files.group_by { |file| detect_language(file) }
151
153
 
152
154
  # Process each language group
153
155
  files_by_lang.each do |lang, lang_files|
154
- puts "📝 Processing #{lang_files.length} #{lang} files..."
156
+ display_message("📝 Processing #{lang_files.length} #{lang} files...", type: :info)
155
157
 
156
158
  # Load grammar for this language
157
159
  grammar = @grammar_loader.load_grammar(lang)
@@ -614,7 +616,7 @@ module Aidp
614
616
  end
615
617
 
616
618
  def write_kb_files
617
- puts "💾 Writing knowledge base files..."
619
+ display_message("💾 Writing knowledge base files...", type: :info)
618
620
 
619
621
  prepare_kb_dir
620
622
 
@@ -637,7 +639,7 @@ module Aidp
637
639
  def write_json_file(filename, data)
638
640
  file_path = File.join(@kb_dir, filename)
639
641
  File.write(file_path, JSON.pretty_generate(data))
640
- puts "📄 Written #{filename} (#{data.length} entries)"
642
+ display_message("📄 Written #{filename} (#{data.length} entries)", type: :success)
641
643
  end
642
644
 
643
645
  def generate_hotspots
@@ -681,6 +683,21 @@ module Aidp
681
683
  # This would analyze test file naming and content
682
684
  []
683
685
  end
686
+
687
+ private
688
+
689
+ # Helper method for consistent message display using TTY::Prompt
690
+ def display_message(message, type: :info)
691
+ color = case type
692
+ when :error then :red
693
+ when :warn then :yellow
694
+ when :success then :green
695
+ when :highlight then :cyan
696
+ else :white
697
+ end
698
+
699
+ @prompt.say(message, color: color)
700
+ end
684
701
  end
685
702
  end
686
703
  end
@@ -10,40 +10,53 @@ module Aidp
10
10
  class FirstRunWizard
11
11
  TEMPLATES_DIR = File.expand_path(File.join(__dir__, "..", "..", "..", "templates"))
12
12
 
13
- def self.ensure_config(project_dir, input: $stdin, output: $stdout, non_interactive: false, prompt: TTY::Prompt.new)
13
+ def self.ensure_config(project_dir, non_interactive: false, prompt: TTY::Prompt.new)
14
14
  return true if Aidp::Config.config_exists?(project_dir)
15
15
 
16
- wizard = new(project_dir, input: input, output: output, prompt: prompt)
16
+ wizard = new(project_dir, prompt: prompt)
17
17
 
18
- if non_interactive || !input.tty? || !output.tty?
18
+ if non_interactive
19
19
  # Non-interactive environment - create minimal config silently
20
20
  path = wizard.send(:write_minimal_config, project_dir)
21
- output.puts "Created minimal configuration at #{wizard.send(:relative, path)} (non-interactive default)"
21
+ wizard.send(:display_message, "Created minimal configuration at #{wizard.send(:relative, path)} (non-interactive default)", type: :success)
22
22
  return true
23
23
  end
24
24
 
25
25
  wizard.run
26
26
  end
27
27
 
28
- def self.setup_config(project_dir, input: $stdin, output: $stdout, non_interactive: false, prompt: TTY::Prompt.new)
29
- wizard = new(project_dir, input: input, output: output, prompt: prompt)
28
+ def self.setup_config(project_dir, non_interactive: false, prompt: TTY::Prompt.new)
29
+ wizard = new(project_dir, prompt: prompt)
30
30
 
31
- if non_interactive || !input.tty? || !output.tty?
31
+ if non_interactive
32
32
  # Non-interactive environment - skip setup
33
- output.puts "Configuration setup skipped in non-interactive environment"
33
+ wizard.send(:display_message, "Configuration setup skipped in non-interactive environment", type: :info)
34
34
  return true
35
35
  end
36
36
 
37
37
  wizard.run_setup_config
38
38
  end
39
39
 
40
- def initialize(project_dir, input: $stdin, output: $stdout, prompt: TTY::Prompt.new)
40
+ def initialize(project_dir, prompt: TTY::Prompt.new)
41
41
  @project_dir = project_dir
42
- @input = input
43
- @output = output
44
42
  @prompt = prompt
45
43
  end
46
44
 
45
+ # Helper method for consistent message display using TTY::Prompt
46
+ def display_message(message, type: :info)
47
+ color = case type
48
+ when :error then :red
49
+ when :success then :green
50
+ when :warning then :yellow
51
+ when :info then :blue
52
+ when :highlight then :cyan
53
+ when :muted then :bright_black
54
+ else :white
55
+ end
56
+
57
+ @prompt.say(message, color: color)
58
+ end
59
+
47
60
  def run
48
61
  banner
49
62
  loop do
@@ -51,10 +64,10 @@ module Aidp
51
64
  case choice
52
65
  when "1" then return finish(write_quick_config(@project_dir))
53
66
  when "2" then return finish(run_custom)
54
- when "q", "Q" then @output.puts("Exiting without creating configuration.")
67
+ when "q", "Q" then display_message("Exiting without creating configuration.")
55
68
  return false
56
69
  else
57
- @output.puts "Invalid selection. Please choose one of the listed options."
70
+ display_message("Invalid selection. Please choose one of the listed options.", type: :warning)
58
71
  end
59
72
  end
60
73
  end
@@ -81,14 +94,14 @@ module Aidp
81
94
  private
82
95
 
83
96
  def banner
84
- @output.puts "\n🚀 First-time setup detected"
85
- @output.puts "No 'aidp.yml' configuration file found in #{relative(@project_dir)}."
86
- @output.puts "Let's create one so you can start using AI Dev Pipeline."
87
- @output.puts
97
+ display_message("\n🚀 First-time setup detected", type: :highlight)
98
+ display_message("No 'aidp.yml' configuration file found in #{relative(@project_dir)}.")
99
+ display_message("Let's create one so you can start using AI Dev Pipeline.")
100
+ display_message("")
88
101
  end
89
102
 
90
103
  def ask_choice
91
- @output.puts "Choose a configuration style:" unless @asking
104
+ display_message("Choose a configuration style:") unless @asking
92
105
 
93
106
  options = {
94
107
  "Quick setup (cursor + macos, no API keys needed)" => "1",
@@ -101,11 +114,11 @@ module Aidp
101
114
 
102
115
  def finish(path)
103
116
  if path
104
- @output.puts "\n✅ Configuration created at #{relative(path)}"
105
- @output.puts "You can edit this file anytime. Continuing startup...\n"
117
+ display_message("\n✅ Configuration created at #{relative(path)}", type: :success)
118
+ display_message("You can edit this file anytime. Continuing startup...\n")
106
119
  true
107
120
  else
108
- @output.puts "❌ Failed to create configuration file."
121
+ display_message("❌ Failed to create configuration file.", type: :error)
109
122
  false
110
123
  end
111
124
  end
@@ -113,7 +126,7 @@ module Aidp
113
126
  def copy_template(filename)
114
127
  src = File.join(TEMPLATES_DIR, filename)
115
128
  unless File.exist?(src)
116
- @output.puts "Template not found: #{filename}"
129
+ display_message("Template not found: #{filename}", type: :error)
117
130
  return nil
118
131
  end
119
132
  dest = File.join(@project_dir, "aidp.yml")
@@ -310,14 +323,10 @@ module Aidp
310
323
 
311
324
  def ask(prompt, default: nil)
312
325
  if default
313
- @output.print "#{prompt} [#{default}]: "
326
+ @prompt.ask("#{prompt}:", default: default)
314
327
  else
315
- @output.print "#{prompt}: "
328
+ @prompt.ask("#{prompt}:")
316
329
  end
317
- @output.flush
318
- ans = @input.gets&.strip
319
- return default if (ans.nil? || ans.empty?) && default
320
- ans
321
330
  end
322
331
 
323
332
  def relative(path)
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "tty-prompt"
3
4
  require "tty-box"
4
5
  require "pastel"
5
6
  require "io/console"
@@ -10,8 +11,9 @@ require_relative "../storage/file_manager"
10
11
  module Aidp
11
12
  class CLI
12
13
  class JobsCommand
13
- def initialize(input: $stdin, output: $stdout)
14
- @io = TerminalIO.new(input, output)
14
+ def initialize(input: nil, output: nil, prompt: TTY::Prompt.new)
15
+ @io = TerminalIO.new(input: input, output: output)
16
+ @prompt = prompt
15
17
  @pastel = Pastel.new
16
18
  @running = true
17
19
  @view_mode = :list
@@ -21,18 +23,35 @@ module Aidp
21
23
  @screen_width = 80 # Default screen width
22
24
  end
23
25
 
26
+ private
27
+
28
+ def display_message(message, type: :info)
29
+ color = case type
30
+ when :error then :red
31
+ when :success then :green
32
+ when :warning then :yellow
33
+ when :info then :blue
34
+ when :highlight then :cyan
35
+ when :muted then :bright_black
36
+ else :white
37
+ end
38
+ @prompt.say(message, color: color)
39
+ end
40
+
41
+ public
42
+
24
43
  def run
25
44
  # Simple harness jobs display
26
45
  jobs = fetch_harness_jobs
27
46
 
28
47
  if jobs.empty?
29
- @io.puts "Harness Jobs"
30
- @io.puts "-" * @screen_width
31
- @io.puts
32
- @io.puts "No harness jobs found"
33
- @io.puts
34
- @io.puts "Harness jobs are background tasks that run during harness mode."
35
- @io.puts "They are stored as JSON files in the .aidp/harness_logs/ directory."
48
+ display_message("Harness Jobs", type: :info)
49
+ display_message("-" * @screen_width, type: :muted)
50
+ display_message("")
51
+ display_message("No harness jobs found", type: :info)
52
+ display_message("")
53
+ display_message("Harness jobs are background tasks that run during harness mode.", type: :info)
54
+ display_message("They are stored as JSON files in the .aidp/harness_logs/ directory.", type: :info)
36
55
  else
37
56
  render_harness_jobs(jobs)
38
57
  end
@@ -64,7 +83,7 @@ module Aidp
64
83
 
65
84
  jobs << job_info
66
85
  rescue JSON::ParserError => e
67
- @io.puts "Warning: Could not parse harness log #{log_file}: #{e.message}" if ENV["AIDP_DEBUG"]
86
+ display_message("Warning: Could not parse harness log #{log_file}: #{e.message}", type: :warning) if ENV["AIDP_DEBUG"]
68
87
  end
69
88
 
70
89
  # Sort by creation time (newest first)
@@ -91,9 +110,9 @@ module Aidp
91
110
 
92
111
  # Render harness jobs in a simple table
93
112
  def render_harness_jobs(jobs)
94
- @io.puts "Harness Jobs"
95
- @io.puts "-" * @screen_width
96
- @io.puts
113
+ display_message("Harness Jobs", type: :info)
114
+ display_message("-" * @screen_width, type: :muted)
115
+ display_message("")
97
116
 
98
117
  # Create job content for TTY::Box
99
118
  job_content = []
@@ -121,12 +140,12 @@ module Aidp
121
140
  border: :thick,
122
141
  padding: [1, 2]
123
142
  )
124
- puts box
143
+ display_message(box)
125
144
 
126
- @io.puts
127
- @io.puts "Total: #{jobs.length} harness job(s)"
128
- @io.puts
129
- @io.puts "Note: Harness jobs are stored as JSON files in .aidp/harness_logs/"
145
+ display_message("")
146
+ display_message("Total: #{jobs.length} harness job(s)", type: :info)
147
+ display_message("")
148
+ display_message("Note: Harness jobs are stored as JSON files in .aidp/harness_logs/", type: :muted)
130
149
  end
131
150
 
132
151
  # Format timestamp for display
@@ -5,9 +5,9 @@ require "stringio"
5
5
  module Aidp
6
6
  class CLI
7
7
  class TerminalIO
8
- def initialize(input = $stdin, output = $stdout)
9
- @input = input
10
- @output = output
8
+ def initialize(input: nil, output: nil)
9
+ @input = input || $stdin
10
+ @output = output || $stdout
11
11
  end
12
12
 
13
13
  def ready?