aidp 0.25.0 → 0.27.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +45 -6
  3. data/lib/aidp/analyze/error_handler.rb +11 -0
  4. data/lib/aidp/cli/checkpoint_command.rb +198 -0
  5. data/lib/aidp/cli/config_command.rb +71 -0
  6. data/lib/aidp/cli/enhanced_input.rb +2 -0
  7. data/lib/aidp/cli/first_run_wizard.rb +8 -7
  8. data/lib/aidp/cli/harness_command.rb +102 -0
  9. data/lib/aidp/cli/jobs_command.rb +3 -3
  10. data/lib/aidp/cli/mcp_dashboard.rb +4 -3
  11. data/lib/aidp/cli/models_command.rb +662 -0
  12. data/lib/aidp/cli/providers_command.rb +223 -0
  13. data/lib/aidp/cli.rb +35 -456
  14. data/lib/aidp/daemon/runner.rb +2 -2
  15. data/lib/aidp/debug_mixin.rb +2 -9
  16. data/lib/aidp/execute/async_work_loop_runner.rb +2 -1
  17. data/lib/aidp/execute/checkpoint_display.rb +38 -37
  18. data/lib/aidp/execute/interactive_repl.rb +2 -1
  19. data/lib/aidp/execute/prompt_manager.rb +4 -4
  20. data/lib/aidp/execute/work_loop_runner.rb +253 -56
  21. data/lib/aidp/execute/workflow_selector.rb +2 -2
  22. data/lib/aidp/harness/config_loader.rb +20 -11
  23. data/lib/aidp/harness/config_manager.rb +5 -5
  24. data/lib/aidp/harness/config_schema.rb +30 -8
  25. data/lib/aidp/harness/configuration.rb +105 -4
  26. data/lib/aidp/harness/enhanced_runner.rb +24 -15
  27. data/lib/aidp/harness/error_handler.rb +26 -5
  28. data/lib/aidp/harness/filter_strategy.rb +45 -0
  29. data/lib/aidp/harness/generic_filter_strategy.rb +63 -0
  30. data/lib/aidp/harness/model_cache.rb +269 -0
  31. data/lib/aidp/harness/model_discovery_service.rb +259 -0
  32. data/lib/aidp/harness/model_registry.rb +201 -0
  33. data/lib/aidp/harness/output_filter.rb +136 -0
  34. data/lib/aidp/harness/provider_manager.rb +18 -3
  35. data/lib/aidp/harness/rspec_filter_strategy.rb +82 -0
  36. data/lib/aidp/harness/runner.rb +5 -0
  37. data/lib/aidp/harness/test_runner.rb +165 -27
  38. data/lib/aidp/harness/thinking_depth_manager.rb +223 -7
  39. data/lib/aidp/harness/ui/enhanced_tui.rb +4 -1
  40. data/lib/aidp/logger.rb +35 -5
  41. data/lib/aidp/providers/adapter.rb +2 -4
  42. data/lib/aidp/providers/anthropic.rb +141 -128
  43. data/lib/aidp/providers/base.rb +98 -2
  44. data/lib/aidp/providers/capability_registry.rb +0 -1
  45. data/lib/aidp/providers/codex.rb +49 -67
  46. data/lib/aidp/providers/cursor.rb +71 -59
  47. data/lib/aidp/providers/gemini.rb +44 -60
  48. data/lib/aidp/providers/github_copilot.rb +2 -66
  49. data/lib/aidp/providers/kilocode.rb +24 -80
  50. data/lib/aidp/providers/opencode.rb +24 -80
  51. data/lib/aidp/safe_directory.rb +10 -3
  52. data/lib/aidp/setup/wizard.rb +345 -8
  53. data/lib/aidp/storage/csv_storage.rb +9 -3
  54. data/lib/aidp/storage/file_manager.rb +8 -2
  55. data/lib/aidp/storage/json_storage.rb +9 -3
  56. data/lib/aidp/version.rb +1 -1
  57. data/lib/aidp/watch/build_processor.rb +40 -1
  58. data/lib/aidp/watch/change_request_processor.rb +659 -0
  59. data/lib/aidp/watch/plan_generator.rb +93 -14
  60. data/lib/aidp/watch/plan_processor.rb +71 -8
  61. data/lib/aidp/watch/repository_client.rb +85 -20
  62. data/lib/aidp/watch/review_processor.rb +3 -3
  63. data/lib/aidp/watch/runner.rb +37 -0
  64. data/lib/aidp/watch/state_store.rb +46 -1
  65. data/lib/aidp/workflows/guided_agent.rb +3 -3
  66. data/lib/aidp/workstream_executor.rb +5 -2
  67. data/lib/aidp.rb +4 -0
  68. data/templates/aidp-development.yml.example +2 -2
  69. data/templates/aidp-production.yml.example +3 -3
  70. data/templates/aidp.yml.example +53 -0
  71. metadata +14 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 348238e998f8d75ef9f23fcb5a8f4517b6fde0dd9612a4520583b03ab692cc23
4
- data.tar.gz: ed8db8dbd0459211e65b76784b6074b65f2689cdb76f7b7c74bcb029d4c9f74d
3
+ metadata.gz: 174c58fcccbc5d5c927cb30dd3bf8c735de926cf52de5eb2a4bb56f2bc984cc3
4
+ data.tar.gz: 73efb11b31d3ff346a482fde9616047fd64525e44ffe768a81a6e4808e748cd1
5
5
  SHA512:
6
- metadata.gz: ac5628709db6248036d75e5873252089e497b96bac785a610bcc9b153b350714d4a5e441610501b3897cf6994d9081fa36e990da67678b86c167d5e58b048f64
7
- data.tar.gz: dd660df245ffd1bb28048e4b76b231efce515e880fc8cfa088af6a25b1fcb42b98562d592880d256ade4474a3db6879567f3fb2670e750da0ee94ea5347b2df7
6
+ metadata.gz: 407aecc4942fbda7fbf09e15ebfdfa6ba8595be9f282a0b425ce65dc22e45d3a524fe5dd211ce96ec59029801df01528a835412ba38f90cb273c5b1594df2016
7
+ data.tar.gz: 174a42200bb01198f3c88de3ec35198c9475f018f06a673e7d1d3c0e0dda9c294dd832519327261ac767a42323e965f1fa7ea1fa1d3b394425604ceaaa7bf371
data/README.md CHANGED
@@ -218,7 +218,9 @@ aidp watch owner/repo --once
218
218
 
219
219
  **Label Workflow:**
220
220
 
221
- AIDP uses a smart label-based workflow to manage the lifecycle of automated issue resolution:
221
+ AIDP uses a smart label-based workflow to manage both issues and pull requests:
222
+
223
+ #### Issue Workflow (Plan & Build)
222
224
 
223
225
  1. **Planning Phase** (`aidp-plan` label):
224
226
  - Add this label to an issue to trigger plan generation
@@ -247,6 +249,32 @@ AIDP uses a smart label-based workflow to manage the lifecycle of automated issu
247
249
  - Posts completion comment with summary
248
250
  - Automatically removes the `aidp-build` label
249
251
 
252
+ #### Pull Request Workflow (Review, CI Fix, Change Requests)
253
+
254
+ <!-- markdownlint-disable-next-line MD029 -->
255
+ 1. **Code Review** (`aidp-review` label):
256
+ - Add this label to any PR to trigger automated code review
257
+ - AIDP analyzes code from three expert perspectives (Senior Developer, Security Specialist, Performance Analyst)
258
+ - Posts a comprehensive review comment with severity-categorized findings (High Priority, Major, Minor, Nit)
259
+ - Automatically removes the label after posting review
260
+ - No commits are made - review only
261
+
262
+ 2. **CI Fix** (`aidp-fix-ci` label):
263
+ - Add this label to a PR with failing CI checks
264
+ - AIDP analyzes CI failure logs and identifies root causes
265
+ - Automatically fixes issues like linting errors, simple test failures, and dependency problems
266
+ - Commits and pushes fixes to the PR branch
267
+ - Posts a summary of what was fixed
268
+ - Automatically removes the label after completion
269
+
270
+ 3. **Change Requests** (`aidp-request-changes` label):
271
+ - Comment on your own PR describing desired changes, then add this label
272
+ - AIDP implements the requested changes on the PR branch
273
+ - Runs tests/linters and commits changes
274
+ - **If clarification needed**: Replaces label with `aidp-needs-input` and posts questions
275
+ - User responds to questions and re-applies the label to continue
276
+ - Automatically removes the label after completion
277
+
250
278
  **Customizable Labels:**
251
279
 
252
280
  All label names are configurable to match your repository's existing label scheme. Configure via the interactive wizard or manually in `aidp.yml`:
@@ -255,10 +283,16 @@ All label names are configurable to match your repository's existing label schem
255
283
  # .aidp/aidp.yml
256
284
  watch:
257
285
  labels:
258
- plan_trigger: aidp-plan # Label to trigger plan generation
259
- needs_input: aidp-needs-input # Label when plan needs user input
260
- ready_to_build: aidp-ready # Label when plan is ready to build
261
- build_trigger: aidp-build # Label to trigger implementation
286
+ # Issue-based automation
287
+ plan_trigger: aidp-plan # Trigger plan generation
288
+ needs_input: aidp-needs-input # Needs user input/clarification
289
+ ready_to_build: aidp-ready # Plan ready for implementation
290
+ build_trigger: aidp-build # Trigger implementation
291
+
292
+ # PR-based automation
293
+ review_trigger: aidp-review # Trigger code review
294
+ ci_fix_trigger: aidp-fix-ci # Trigger CI auto-fix
295
+ change_request_trigger: aidp-request-changes # Trigger PR change implementation
262
296
  ```
263
297
 
264
298
  Run `aidp config --interactive` and enable watch mode to configure labels interactively.
@@ -294,7 +328,12 @@ AIDP can automatically request clarification when it needs more information duri
294
328
 
295
329
  This ensures AIDP never gets stuck - if it needs more information, it will ask for it rather than making incorrect assumptions or failing silently.
296
330
 
297
- See [Watch Mode Guide](docs/FULLY_AUTOMATIC_MODE.md) and [Watch Mode Safety](docs/WATCH_MODE_SAFETY.md) for complete documentation.
331
+ **Additional Documentation:**
332
+
333
+ - [Watch Mode Guide](docs/FULLY_AUTOMATIC_MODE.md) - Complete guide to watch mode setup and operation
334
+ - [Watch Mode Safety](docs/WATCH_MODE_SAFETY.md) - Security features and best practices
335
+ - [PR Automation Guide](docs/PR_AUTOMATION.md) - Detailed guide for code review, CI fixes, and PR changes
336
+ - [PR Change Requests](docs/PR_CHANGE_REQUESTS.md) - Comprehensive documentation for automated PR modifications
298
337
 
299
338
  ## Command Reference
300
339
 
@@ -99,6 +99,13 @@ module Aidp
99
99
  private
100
100
 
101
101
  def setup_logger(log_file, verbose)
102
+ # Suppress logger output in test/CI environments
103
+ if suppress_error_logs?
104
+ logger = ::Logger.new(IO::NULL)
105
+ logger.level = ::Logger::FATAL
106
+ return logger
107
+ end
108
+
102
109
  output_stream = log_file || @output || $stdout
103
110
  logger = ::Logger.new(output_stream)
104
111
  logger.level = verbose ? ::Logger::DEBUG : ::Logger::INFO
@@ -108,6 +115,10 @@ module Aidp
108
115
  logger
109
116
  end
110
117
 
118
+ def suppress_error_logs?
119
+ ENV["RSPEC_RUNNING"] || ENV["CI"] || ENV["RAILS_ENV"] == "test" || ENV["RACK_ENV"] == "test"
120
+ end
121
+
111
122
  def setup_recovery_strategies
112
123
  strategies = {
113
124
  Errno::ENOENT => :skip_step_with_warning,
@@ -0,0 +1,198 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "tty-prompt"
4
+ require_relative "../execute/checkpoint"
5
+ require_relative "../execute/checkpoint_display"
6
+
7
+ module Aidp
8
+ class CLI
9
+ # Command handler for `aidp checkpoint` subcommand
10
+ #
11
+ # Provides commands for managing workflow checkpoints:
12
+ # - show: Display latest checkpoint data
13
+ # - summary: Show progress summary with trends
14
+ # - history: Show last N checkpoints
15
+ # - metrics: Show detailed metrics
16
+ # - clear: Clear all checkpoint data
17
+ #
18
+ # Usage:
19
+ # aidp checkpoint show
20
+ # aidp checkpoint summary --watch
21
+ # aidp checkpoint history 10
22
+ # aidp checkpoint metrics
23
+ # aidp checkpoint clear --force
24
+ class CheckpointCommand
25
+ include Aidp::MessageDisplay
26
+
27
+ def initialize(prompt: TTY::Prompt.new, checkpoint_class: nil, display_class: nil, project_dir: nil)
28
+ @prompt = prompt
29
+ @checkpoint_class = checkpoint_class || Aidp::Execute::Checkpoint
30
+ @display_class = display_class || Aidp::Execute::CheckpointDisplay
31
+ @project_dir = project_dir || Dir.pwd
32
+ end
33
+
34
+ # Main entry point for checkpoint subcommands
35
+ def run(args)
36
+ sub = args.shift || "summary"
37
+ checkpoint = @checkpoint_class.new(@project_dir)
38
+ display = @display_class.new
39
+
40
+ case sub
41
+ when "show"
42
+ run_show_command(checkpoint, display)
43
+ when "summary"
44
+ run_summary_command(checkpoint, display, args)
45
+ when "history"
46
+ run_history_command(checkpoint, display, args)
47
+ when "metrics"
48
+ run_metrics_command(checkpoint)
49
+ when "clear"
50
+ run_clear_command(checkpoint, args)
51
+ else
52
+ display_usage
53
+ end
54
+ end
55
+
56
+ private
57
+
58
+ def run_show_command(checkpoint, display)
59
+ latest = checkpoint.latest_checkpoint
60
+ if latest
61
+ display.display_checkpoint(latest, show_details: true)
62
+ else
63
+ display_message("No checkpoint data found.", type: :info)
64
+ end
65
+ end
66
+
67
+ def run_summary_command(checkpoint, display, args)
68
+ watch = args.include?("--watch")
69
+ interval = extract_interval_option(args) || 5
70
+
71
+ if watch
72
+ watch_checkpoint_summary(checkpoint, display, interval)
73
+ else
74
+ summary = checkpoint.progress_summary
75
+ if summary
76
+ display.display_progress_summary(summary)
77
+ else
78
+ display_message("No checkpoint data found.", type: :info)
79
+ end
80
+ end
81
+ end
82
+
83
+ def run_history_command(checkpoint, display, args)
84
+ limit = args.shift || "10"
85
+ history = checkpoint.checkpoint_history(limit: limit.to_i)
86
+ if history.any?
87
+ display.display_checkpoint_history(history, limit: limit.to_i)
88
+ else
89
+ display_message("No checkpoint history found.", type: :info)
90
+ end
91
+ end
92
+
93
+ def run_metrics_command(checkpoint)
94
+ latest = checkpoint.latest_checkpoint
95
+ unless latest
96
+ display_message("No checkpoint data found.", type: :info)
97
+ return
98
+ end
99
+
100
+ display_message("", type: :info)
101
+ display_message("šŸ“Š Detailed Metrics", type: :info)
102
+ display_message("=" * 60, type: :muted)
103
+
104
+ metrics = latest[:metrics]
105
+ display_message("Lines of Code: #{metrics[:lines_of_code]}", type: :info)
106
+ display_message("File Count: #{metrics[:file_count]}", type: :info)
107
+ display_message("Test Coverage: #{metrics[:test_coverage]}%", type: :info)
108
+ display_message("Code Quality: #{metrics[:code_quality]}%", type: :info)
109
+ display_message("PRD Task Progress: #{metrics[:prd_task_progress]}%", type: :info)
110
+
111
+ if metrics.key?(:tests_passing)
112
+ status = metrics[:tests_passing] ? "āœ“ Passing" : "āœ— Failing"
113
+ display_message("Tests: #{status}", type: :info)
114
+ end
115
+
116
+ if metrics.key?(:linters_passing)
117
+ status = metrics[:linters_passing] ? "āœ“ Passing" : "āœ— Failing"
118
+ display_message("Linters: #{status}", type: :info)
119
+ end
120
+
121
+ display_message("=" * 60, type: :muted)
122
+ display_message("", type: :info)
123
+ end
124
+
125
+ def run_clear_command(checkpoint, args)
126
+ force = args.include?("--force")
127
+ unless force
128
+ confirm = @prompt.yes?("Are you sure you want to clear all checkpoint data?")
129
+ return unless confirm
130
+ end
131
+
132
+ checkpoint.clear
133
+ display_message("āœ“ Checkpoint data cleared.", type: :success)
134
+ end
135
+
136
+ def watch_checkpoint_summary(checkpoint, display, interval)
137
+ display_message("Watching checkpoint summary (refresh: #{interval}s, Ctrl+C to exit)...", type: :info)
138
+ display_message("", type: :info)
139
+
140
+ begin
141
+ loop do
142
+ # Clear screen
143
+ print "\e[2J\e[H"
144
+
145
+ summary = checkpoint.progress_summary
146
+ if summary
147
+ display.display_progress_summary(summary)
148
+
149
+ # Show last update time
150
+ if summary[:current] && summary[:current][:timestamp]
151
+ last_update = Time.parse(summary[:current][:timestamp])
152
+ age = Time.now - last_update
153
+ display_message("", type: :info)
154
+ display_message("Last update: #{format_time_ago_simple(age)} | Refreshing in #{interval}s...", type: :muted)
155
+ end
156
+ else
157
+ display_message("No checkpoint data found. Waiting for data...", type: :info)
158
+ end
159
+
160
+ sleep interval
161
+ end
162
+ rescue Interrupt
163
+ display_message("\nStopped watching checkpoint summary", type: :info)
164
+ end
165
+ end
166
+
167
+ def format_time_ago_simple(seconds)
168
+ if seconds < 60
169
+ "#{seconds.to_i}s ago"
170
+ elsif seconds < 3600
171
+ "#{(seconds / 60).to_i}m ago"
172
+ else
173
+ "#{(seconds / 3600).to_i}h ago"
174
+ end
175
+ end
176
+
177
+ def extract_interval_option(args)
178
+ args.each_with_index do |arg, i|
179
+ if arg == "--interval" && args[i + 1]
180
+ return args[i + 1].to_i
181
+ elsif arg.start_with?("--interval=")
182
+ return arg.split("=")[1].to_i
183
+ end
184
+ end
185
+ nil
186
+ end
187
+
188
+ def display_usage
189
+ display_message("Usage: aidp checkpoint <show|summary|history|metrics|clear>", type: :info)
190
+ display_message(" show - Show the latest checkpoint data", type: :info)
191
+ display_message(" summary [--watch] - Show progress summary with trends", type: :info)
192
+ display_message(" history [N] - Show last N checkpoints", type: :info)
193
+ display_message(" metrics - Show detailed metrics", type: :info)
194
+ display_message(" clear [--force] - Clear all checkpoint data", type: :info)
195
+ end
196
+ end
197
+ end
198
+ end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "tty-prompt"
4
+ require_relative "../setup/wizard"
5
+
6
+ module Aidp
7
+ class CLI
8
+ # Command handler for `aidp config` subcommand
9
+ #
10
+ # Provides commands for managing AIDP configuration:
11
+ # - Interactive configuration wizard
12
+ # - Dry-run mode for testing configuration changes
13
+ #
14
+ # Usage:
15
+ # aidp config --interactive
16
+ # aidp config --interactive --dry-run
17
+ class ConfigCommand
18
+ include Aidp::MessageDisplay
19
+
20
+ def initialize(prompt: TTY::Prompt.new, wizard_class: nil, project_dir: nil)
21
+ @prompt = prompt
22
+ @wizard_class = wizard_class || Aidp::Setup::Wizard
23
+ @project_dir = project_dir || Dir.pwd
24
+ end
25
+
26
+ # Main entry point for config command
27
+ def run(args)
28
+ interactive = false
29
+ dry_run = false
30
+
31
+ until args.empty?
32
+ token = args.shift
33
+ case token
34
+ when "--interactive"
35
+ interactive = true
36
+ when "--dry-run"
37
+ dry_run = true
38
+ when "-h", "--help"
39
+ display_usage
40
+ return
41
+ else
42
+ display_message("Unknown option: #{token}", type: :error)
43
+ display_usage
44
+ return
45
+ end
46
+ end
47
+
48
+ unless interactive
49
+ display_usage
50
+ return
51
+ end
52
+
53
+ wizard = @wizard_class.new(@project_dir, prompt: @prompt, dry_run: dry_run)
54
+ wizard.run
55
+ end
56
+
57
+ private
58
+
59
+ def display_usage
60
+ display_message("\nUsage: aidp config --interactive [--dry-run]", type: :info)
61
+ display_message("\nOptions:", type: :info)
62
+ display_message(" --interactive Run interactive configuration wizard", type: :info)
63
+ display_message(" --dry-run Perform a dry run without making changes", type: :info)
64
+ display_message(" -h, --help Show this help message", type: :info)
65
+ display_message("\nExamples:", type: :info)
66
+ display_message(" aidp config --interactive", type: :info)
67
+ display_message(" aidp config --interactive --dry-run", type: :info)
68
+ end
69
+ end
70
+ end
71
+ end
@@ -21,6 +21,8 @@ module Aidp
21
21
  # - Ctrl-T: Transpose characters
22
22
  # - And many more Emacs-style bindings
23
23
 
24
+ attr_reader :use_reline, :show_hints
25
+
24
26
  def initialize(prompt: nil, input: nil, output: nil, use_reline: true)
25
27
  @use_reline = use_reline
26
28
  @input = input || $stdin
@@ -10,10 +10,10 @@ module Aidp
10
10
  class FirstRunWizard
11
11
  include Aidp::MessageDisplay
12
12
 
13
- def self.ensure_config(project_dir, non_interactive: false, prompt: TTY::Prompt.new)
13
+ def self.ensure_config(project_dir, non_interactive: false, prompt: TTY::Prompt.new, wizard_class: Aidp::Setup::Wizard)
14
14
  return true if Aidp::Config.config_exists?(project_dir)
15
15
 
16
- wizard = new(project_dir, prompt: prompt)
16
+ wizard = new(project_dir, prompt: prompt, wizard_class: wizard_class)
17
17
 
18
18
  if non_interactive
19
19
  wizard.create_minimal_config
@@ -24,22 +24,23 @@ module Aidp
24
24
  end
25
25
  end
26
26
 
27
- def self.setup_config(project_dir, non_interactive: false, prompt: TTY::Prompt.new)
27
+ def self.setup_config(project_dir, non_interactive: false, prompt: TTY::Prompt.new, wizard_class: Aidp::Setup::Wizard)
28
28
  if non_interactive
29
- new(project_dir, prompt: prompt).send(:display_message, "Configuration setup skipped in non-interactive environment", type: :info)
29
+ new(project_dir, prompt: prompt, wizard_class: wizard_class).send(:display_message, "Configuration setup skipped in non-interactive environment", type: :info)
30
30
  return true
31
31
  end
32
32
 
33
- new(project_dir, prompt: prompt).run
33
+ new(project_dir, prompt: prompt, wizard_class: wizard_class).run
34
34
  end
35
35
 
36
- def initialize(project_dir, prompt: TTY::Prompt.new)
36
+ def initialize(project_dir, prompt: TTY::Prompt.new, wizard_class: Aidp::Setup::Wizard)
37
37
  @project_dir = project_dir
38
38
  @prompt = prompt
39
+ @wizard_class = wizard_class
39
40
  end
40
41
 
41
42
  def run
42
- wizard = Aidp::Setup::Wizard.new(@project_dir, prompt: @prompt)
43
+ wizard = @wizard_class.new(@project_dir, prompt: @prompt)
43
44
  wizard.run
44
45
  end
45
46
 
@@ -0,0 +1,102 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "tty-prompt"
4
+ require_relative "../harness/runner"
5
+
6
+ module Aidp
7
+ class CLI
8
+ # Command handler for `aidp harness status` and `aidp harness reset` subcommands
9
+ #
10
+ # Provides commands for viewing and managing harness state:
11
+ # - status: Show detailed harness status for all modes
12
+ # - reset: Reset harness state for a specific mode
13
+ #
14
+ # Usage:
15
+ # aidp harness status
16
+ # aidp harness reset --mode analyze
17
+ class HarnessCommand
18
+ include Aidp::MessageDisplay
19
+ include Aidp::RescueLogging
20
+
21
+ def initialize(prompt: TTY::Prompt.new, runner_class: nil, project_dir: nil)
22
+ @prompt = prompt
23
+ @runner_class = runner_class || Aidp::Harness::Runner
24
+ @project_dir = project_dir || Dir.pwd
25
+ end
26
+
27
+ # Main entry point for harness status/reset subcommands
28
+ def run(args, subcommand:, options: {})
29
+ case subcommand
30
+ when "status"
31
+ run_status_command
32
+ when "reset"
33
+ run_reset_command(options)
34
+ else
35
+ display_message("Unknown harness subcommand: #{subcommand}", type: :error)
36
+ display_help
37
+ 1
38
+ end
39
+ end
40
+
41
+ private
42
+
43
+ def run_status_command
44
+ modes = %i[analyze execute]
45
+ display_message("šŸ”§ Harness Status", type: :highlight)
46
+ modes.each do |mode|
47
+ status = fetch_harness_status(mode)
48
+ print_harness_mode_status(mode, status)
49
+ end
50
+ end
51
+
52
+ def run_reset_command(options)
53
+ mode = (options[:mode] || "analyze").to_s
54
+ unless %w[analyze execute].include?(mode)
55
+ display_message("āŒ Invalid mode. Use 'analyze' or 'execute'", type: :error)
56
+ return
57
+ end
58
+
59
+ # Build a runner to access state manager
60
+ runner = @runner_class.new(@project_dir, mode.to_sym, {})
61
+ state_manager = runner.instance_variable_get(:@state_manager)
62
+ state_manager.reset_all if state_manager.respond_to?(:reset_all)
63
+ display_message("āœ… Reset harness state for #{mode} mode", type: :success)
64
+ end
65
+
66
+ def fetch_harness_status(mode)
67
+ runner = @runner_class.new(@project_dir, mode, {})
68
+ if runner.respond_to?(:detailed_status)
69
+ runner.detailed_status
70
+ else
71
+ {harness: {state: "unknown"}}
72
+ end
73
+ rescue => e
74
+ log_rescue(e, component: "harness_command", action: "fetch_harness_status", fallback: {harness: {state: "error"}}, mode: mode)
75
+ {harness: {state: "error", error: e.message}}
76
+ end
77
+
78
+ def print_harness_mode_status(mode, status)
79
+ harness = status[:harness] || {}
80
+ display_message("\nšŸ“‹ #{mode.to_s.capitalize} Mode:", type: :info)
81
+ display_message(" State: #{harness[:state]}", type: :info)
82
+ if harness[:progress]
83
+ prog = harness[:progress]
84
+ display_message(" Progress: #{prog[:completed_steps]}/#{prog[:total_steps]}", type: :success)
85
+ display_message(" Current Step: #{harness[:current_step]}", type: :info) if harness[:current_step]
86
+ end
87
+ end
88
+
89
+ def display_help
90
+ display_message("\nUsage: aidp harness <subcommand> [options]", type: :info)
91
+ display_message("\nSubcommands:", type: :info)
92
+ display_message(" status Show harness status for all modes", type: :info)
93
+ display_message(" reset Reset harness state for a mode", type: :info)
94
+ display_message("\nOptions:", type: :info)
95
+ display_message(" --mode MODE Specify mode for reset (analyze|execute)", type: :info)
96
+ display_message("\nExamples:", type: :info)
97
+ display_message(" aidp harness status", type: :info)
98
+ display_message(" aidp harness reset --mode analyze", type: :info)
99
+ end
100
+ end
101
+ end
102
+ end
@@ -14,7 +14,7 @@ module Aidp
14
14
  class JobsCommand
15
15
  include Aidp::MessageDisplay
16
16
 
17
- def initialize(input: nil, output: nil, prompt: TTY::Prompt.new)
17
+ def initialize(input: nil, output: nil, prompt: TTY::Prompt.new, file_manager: nil, background_runner: nil)
18
18
  @io = TerminalIO.new(input: input, output: output)
19
19
  @prompt = prompt
20
20
  @pastel = Pastel.new
@@ -22,8 +22,8 @@ module Aidp
22
22
  @view_mode = :list
23
23
  @selected_job_id = nil
24
24
  @jobs_displayed = false # Track if we've displayed jobs in interactive mode
25
- @file_manager = Aidp::Storage::FileManager.new(File.join(Dir.pwd, ".aidp"))
26
- @background_runner = Aidp::Jobs::BackgroundRunner.new(Dir.pwd)
25
+ @file_manager = file_manager || Aidp::Storage::FileManager.new(File.join(Dir.pwd, ".aidp"))
26
+ @background_runner = background_runner || Aidp::Jobs::BackgroundRunner.new(Dir.pwd)
27
27
  @screen_width = 80 # Default screen width
28
28
  end
29
29
 
@@ -10,9 +10,10 @@ module Aidp
10
10
  class McpDashboard
11
11
  include Aidp::MessageDisplay
12
12
 
13
- def initialize(root_dir = nil)
13
+ def initialize(root_dir = nil, configuration: nil, provider_info_class: Aidp::Harness::ProviderInfo)
14
14
  @root_dir = root_dir || Dir.pwd
15
- @configuration = Aidp::Harness::Configuration.new(@root_dir)
15
+ @configuration = configuration || Aidp::Harness::Configuration.new(@root_dir)
16
+ @provider_info_class = provider_info_class
16
17
  end
17
18
 
18
19
  # Display MCP dashboard showing all servers across all providers
@@ -101,7 +102,7 @@ module Aidp
101
102
  provider_servers = {} # provider_name => [server_info]
102
103
 
103
104
  providers.each do |provider|
104
- provider_info = Aidp::Harness::ProviderInfo.new(provider, @root_dir)
105
+ provider_info = @provider_info_class.new(provider, @root_dir)
105
106
  info = provider_info.info
106
107
 
107
108
  next unless info[:mcp_support]