aidp 0.32.0 → 0.34.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 (112) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +35 -0
  3. data/lib/aidp/analyze/feature_analyzer.rb +322 -320
  4. data/lib/aidp/analyze/tree_sitter_scan.rb +3 -0
  5. data/lib/aidp/auto_update/coordinator.rb +97 -7
  6. data/lib/aidp/auto_update.rb +0 -12
  7. data/lib/aidp/cli/devcontainer_commands.rb +0 -5
  8. data/lib/aidp/cli/eval_command.rb +399 -0
  9. data/lib/aidp/cli/harness_command.rb +1 -1
  10. data/lib/aidp/cli/security_command.rb +416 -0
  11. data/lib/aidp/cli/tools_command.rb +6 -4
  12. data/lib/aidp/cli.rb +172 -4
  13. data/lib/aidp/comment_consolidator.rb +78 -0
  14. data/lib/aidp/concurrency/exec.rb +3 -0
  15. data/lib/aidp/concurrency.rb +0 -3
  16. data/lib/aidp/config.rb +113 -1
  17. data/lib/aidp/config_paths.rb +91 -0
  18. data/lib/aidp/daemon/runner.rb +8 -4
  19. data/lib/aidp/errors.rb +134 -0
  20. data/lib/aidp/evaluations/context_capture.rb +205 -0
  21. data/lib/aidp/evaluations/evaluation_record.rb +114 -0
  22. data/lib/aidp/evaluations/evaluation_storage.rb +250 -0
  23. data/lib/aidp/evaluations.rb +23 -0
  24. data/lib/aidp/execute/async_work_loop_runner.rb +4 -1
  25. data/lib/aidp/execute/interactive_repl.rb +6 -2
  26. data/lib/aidp/execute/prompt_evaluator.rb +359 -0
  27. data/lib/aidp/execute/repl_macros.rb +100 -1
  28. data/lib/aidp/execute/work_loop_runner.rb +719 -58
  29. data/lib/aidp/execute/work_loop_state.rb +4 -1
  30. data/lib/aidp/execute/workflow_selector.rb +3 -0
  31. data/lib/aidp/harness/ai_decision_engine.rb +79 -0
  32. data/lib/aidp/harness/ai_filter_factory.rb +285 -0
  33. data/lib/aidp/harness/capability_registry.rb +2 -0
  34. data/lib/aidp/harness/condition_detector.rb +3 -0
  35. data/lib/aidp/harness/config_loader.rb +3 -0
  36. data/lib/aidp/harness/config_schema.rb +97 -1
  37. data/lib/aidp/harness/config_validator.rb +1 -1
  38. data/lib/aidp/harness/configuration.rb +61 -5
  39. data/lib/aidp/harness/enhanced_runner.rb +14 -11
  40. data/lib/aidp/harness/error_handler.rb +3 -0
  41. data/lib/aidp/harness/filter_definition.rb +212 -0
  42. data/lib/aidp/harness/generated_filter_strategy.rb +197 -0
  43. data/lib/aidp/harness/output_filter.rb +50 -25
  44. data/lib/aidp/harness/output_filter_config.rb +129 -0
  45. data/lib/aidp/harness/provider_factory.rb +3 -0
  46. data/lib/aidp/harness/provider_manager.rb +96 -2
  47. data/lib/aidp/harness/runner.rb +5 -12
  48. data/lib/aidp/harness/state/persistence.rb +3 -0
  49. data/lib/aidp/harness/state_manager.rb +3 -0
  50. data/lib/aidp/harness/status_display.rb +28 -20
  51. data/lib/aidp/harness/test_runner.rb +179 -41
  52. data/lib/aidp/harness/thinking_depth_manager.rb +44 -28
  53. data/lib/aidp/harness/ui/enhanced_tui.rb +4 -0
  54. data/lib/aidp/harness/ui/enhanced_workflow_selector.rb +4 -0
  55. data/lib/aidp/harness/ui/error_handler.rb +3 -0
  56. data/lib/aidp/harness/ui/job_monitor.rb +4 -0
  57. data/lib/aidp/harness/ui/navigation/submenu.rb +2 -2
  58. data/lib/aidp/harness/ui/navigation/workflow_selector.rb +6 -0
  59. data/lib/aidp/harness/ui/spinner_helper.rb +3 -0
  60. data/lib/aidp/harness/ui/workflow_controller.rb +3 -0
  61. data/lib/aidp/harness/user_interface.rb +3 -0
  62. data/lib/aidp/loader.rb +195 -0
  63. data/lib/aidp/logger.rb +3 -0
  64. data/lib/aidp/message_display.rb +31 -0
  65. data/lib/aidp/metadata/compiler.rb +29 -17
  66. data/lib/aidp/metadata/query.rb +1 -1
  67. data/lib/aidp/metadata/scanner.rb +8 -1
  68. data/lib/aidp/metadata/tool_metadata.rb +13 -13
  69. data/lib/aidp/metadata/validator.rb +10 -0
  70. data/lib/aidp/metadata.rb +16 -0
  71. data/lib/aidp/pr_worktree_manager.rb +20 -8
  72. data/lib/aidp/provider_manager.rb +4 -7
  73. data/lib/aidp/providers/base.rb +2 -0
  74. data/lib/aidp/security/rule_of_two_enforcer.rb +210 -0
  75. data/lib/aidp/security/secrets_proxy.rb +328 -0
  76. data/lib/aidp/security/secrets_registry.rb +227 -0
  77. data/lib/aidp/security/trifecta_state.rb +220 -0
  78. data/lib/aidp/security/watch_mode_handler.rb +306 -0
  79. data/lib/aidp/security/work_loop_adapter.rb +277 -0
  80. data/lib/aidp/security.rb +56 -0
  81. data/lib/aidp/setup/wizard.rb +283 -11
  82. data/lib/aidp/skills.rb +0 -5
  83. data/lib/aidp/storage/csv_storage.rb +3 -0
  84. data/lib/aidp/style_guide/selector.rb +360 -0
  85. data/lib/aidp/tooling_detector.rb +283 -16
  86. data/lib/aidp/version.rb +1 -1
  87. data/lib/aidp/watch/auto_merger.rb +274 -0
  88. data/lib/aidp/watch/auto_pr_processor.rb +125 -7
  89. data/lib/aidp/watch/build_processor.rb +16 -1
  90. data/lib/aidp/watch/change_request_processor.rb +682 -150
  91. data/lib/aidp/watch/ci_fix_processor.rb +262 -4
  92. data/lib/aidp/watch/feedback_collector.rb +191 -0
  93. data/lib/aidp/watch/hierarchical_pr_strategy.rb +256 -0
  94. data/lib/aidp/watch/implementation_verifier.rb +142 -1
  95. data/lib/aidp/watch/plan_generator.rb +70 -13
  96. data/lib/aidp/watch/plan_processor.rb +12 -5
  97. data/lib/aidp/watch/projects_processor.rb +286 -0
  98. data/lib/aidp/watch/repository_client.rb +871 -22
  99. data/lib/aidp/watch/review_processor.rb +33 -6
  100. data/lib/aidp/watch/runner.rb +80 -29
  101. data/lib/aidp/watch/state_store.rb +233 -0
  102. data/lib/aidp/watch/sub_issue_creator.rb +221 -0
  103. data/lib/aidp/watch.rb +5 -7
  104. data/lib/aidp/workflows/guided_agent.rb +4 -0
  105. data/lib/aidp/workstream_cleanup.rb +0 -2
  106. data/lib/aidp/workstream_executor.rb +3 -4
  107. data/lib/aidp/worktree.rb +61 -12
  108. data/lib/aidp/worktree_branch_manager.rb +347 -101
  109. data/lib/aidp.rb +21 -106
  110. data/templates/implementation/iterative_implementation.md +46 -3
  111. metadata +91 -36
  112. data/lib/aidp/config/paths.rb +0 -131
@@ -14,6 +14,9 @@ module Aidp
14
14
  class TreeSitterScan
15
15
  include Aidp::MessageDisplay
16
16
 
17
+ # Expose state for testability
18
+ attr_accessor :symbols, :imports, :calls, :metrics, :seams, :hotspots, :tests, :cycles
19
+
17
20
  def initialize(root: Dir.pwd, kb_dir: ".aidp/kb", langs: %w[ruby], threads: Etc.nprocessors, prompt: TTY::Prompt.new)
18
21
  @root = File.expand_path(root)
19
22
  @kb_dir = File.expand_path(kb_dir, @root)
@@ -1,12 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "version_detector"
4
- require_relative "checkpoint_store"
5
- require_relative "update_logger"
6
- require_relative "failure_tracker"
7
- require_relative "update_policy"
8
- require_relative "errors"
9
-
10
3
  module Aidp
11
4
  module AutoUpdate
12
5
  # Facade for orchestrating the complete auto-update workflow
@@ -188,8 +181,105 @@ module Aidp
188
181
  }
189
182
  end
190
183
 
184
+ # Perform hot code reload without restarting the process
185
+ #
186
+ # This method performs a git pull and then uses Zeitwerk to reload
187
+ # all Ruby classes. Unlike initiate_update (which exits with code 75),
188
+ # this method keeps the process running.
189
+ #
190
+ # @param update_check [UpdateCheck] Update check result
191
+ # @return [Boolean] Whether reload was successful
192
+ # @raise [UpdateError] If updates are disabled or reload fails
193
+ def hot_reload_update(update_check = nil)
194
+ raise UpdateError, "Updates disabled by configuration" unless @policy.enabled
195
+
196
+ # Verify Zeitwerk loader is set up for reloading
197
+ unless Aidp::Loader.setup? && Aidp::Loader.reloading?
198
+ Aidp.log_warn("auto_update_coordinator", "hot_reload_not_available",
199
+ reason: "Zeitwerk loader not configured for reloading")
200
+ raise UpdateError, "Hot reloading not available. Loader must be configured with enable_reloading: true"
201
+ end
202
+
203
+ update_check ||= check_for_update
204
+ return false unless update_check.should_update?
205
+
206
+ from_version = update_check.current_version
207
+ to_version = update_check.available_version
208
+
209
+ Aidp.log_info("auto_update_coordinator", "hot_reload_starting",
210
+ from_version: from_version,
211
+ to_version: to_version)
212
+
213
+ # Perform git pull
214
+ unless perform_git_pull
215
+ raise UpdateError, "Git pull failed"
216
+ end
217
+
218
+ # Reload all classes using Zeitwerk
219
+ unless Aidp::Loader.reload!
220
+ @failure_tracker.record_failure
221
+ @update_logger.log_failure("Zeitwerk reload failed")
222
+ raise UpdateError, "Zeitwerk reload failed"
223
+ end
224
+
225
+ # Log success
226
+ @update_logger.log_success(
227
+ from_version: from_version,
228
+ to_version: to_version
229
+ )
230
+ @failure_tracker.reset_on_success
231
+
232
+ Aidp.log_info("auto_update_coordinator", "hot_reload_complete",
233
+ from_version: from_version,
234
+ to_version: to_version)
235
+
236
+ true
237
+ rescue UpdateError
238
+ raise
239
+ rescue => e
240
+ @failure_tracker.record_failure
241
+ @update_logger.log_failure("Hot reload failed: #{e.message}")
242
+ Aidp.log_error("auto_update_coordinator", "hot_reload_failed",
243
+ error: e.message)
244
+ raise UpdateError, "Hot reload failed: #{e.message}"
245
+ end
246
+
247
+ # Check if hot reloading is available
248
+ # @return [Boolean]
249
+ def hot_reload_available?
250
+ Aidp::Loader.setup? && Aidp::Loader.reloading?
251
+ end
252
+
191
253
  private
192
254
 
255
+ # Perform git pull to fetch latest code
256
+ # @return [Boolean] Whether pull was successful
257
+ def perform_git_pull
258
+ require "open3"
259
+
260
+ Aidp.log_debug("auto_update_coordinator", "git_pull_starting",
261
+ project_dir: @project_dir)
262
+
263
+ Dir.chdir(@project_dir) do
264
+ stdout, stderr, status = Open3.capture3("git", "pull", "--ff-only")
265
+
266
+ if status.success?
267
+ Aidp.log_info("auto_update_coordinator", "git_pull_success",
268
+ output: stdout.strip)
269
+ true
270
+ else
271
+ Aidp.log_error("auto_update_coordinator", "git_pull_failed",
272
+ stderr: stderr.strip,
273
+ exit_code: status.exitstatus)
274
+ false
275
+ end
276
+ end
277
+ rescue => e
278
+ Aidp.log_error("auto_update_coordinator", "git_pull_error",
279
+ error: e.message)
280
+ false
281
+ end
282
+
193
283
  def build_checkpoint(current_state, target_version)
194
284
  Checkpoint.new(
195
285
  mode: current_state[:mode] || "watch",
@@ -1,17 +1,5 @@
1
1
  # frozen_string_literal: true
2
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
3
  module Aidp
16
4
  # Auto-update functionality for Aidp in devcontainers
17
5
  module AutoUpdate
@@ -1,10 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "../setup/devcontainer/parser"
4
- require_relative "../setup/devcontainer/generator"
5
- require_relative "../setup/devcontainer/port_manager"
6
- require_relative "../setup/devcontainer/backup_manager"
7
- require_relative "../message_display"
8
3
  require "json"
9
4
  require "yaml"
10
5
 
@@ -0,0 +1,399 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "tty-prompt"
4
+ require "tty-table"
5
+ require_relative "../evaluations"
6
+ require_relative "../message_display"
7
+
8
+ module Aidp
9
+ class CLI
10
+ # Command handler for `aidp eval` subcommand
11
+ #
12
+ # Provides commands for managing evaluations:
13
+ # - list: List recent evaluations
14
+ # - view <id>: View details of a specific evaluation
15
+ # - stats: Show evaluation statistics
16
+ # - add <rating>: Add a new evaluation
17
+ # - clear: Clear all evaluation data
18
+ #
19
+ # Usage:
20
+ # aidp eval list
21
+ # aidp eval list --rating good
22
+ # aidp eval view eval_20241115_123456_abc1
23
+ # aidp eval stats
24
+ # aidp eval add good "Great output"
25
+ # aidp eval clear --force
26
+ class EvalCommand
27
+ include Aidp::MessageDisplay
28
+
29
+ def initialize(prompt: TTY::Prompt.new, storage: nil, project_dir: nil)
30
+ @prompt = prompt
31
+ @project_dir = project_dir || Dir.pwd
32
+ @storage = storage || Aidp::Evaluations::EvaluationStorage.new(project_dir: @project_dir)
33
+
34
+ Aidp.log_debug("eval_command", "initialize", project_dir: @project_dir)
35
+ end
36
+
37
+ # Main entry point for eval subcommands
38
+ def run(args)
39
+ sub = args.shift || "list"
40
+
41
+ case sub
42
+ when "list"
43
+ run_list_command(args)
44
+ when "view"
45
+ run_view_command(args)
46
+ when "stats"
47
+ run_stats_command
48
+ when "add"
49
+ run_add_command(args)
50
+ when "watch"
51
+ run_watch_command(args)
52
+ when "clear"
53
+ run_clear_command(args)
54
+ else
55
+ display_usage
56
+ end
57
+ end
58
+
59
+ private
60
+
61
+ def run_list_command(args)
62
+ Aidp.log_debug("eval_command", "list", args: args)
63
+
64
+ options = parse_list_options(args)
65
+ evaluations = @storage.list(
66
+ limit: options[:limit],
67
+ rating: options[:rating],
68
+ target_type: options[:target_type]
69
+ )
70
+
71
+ if evaluations.empty?
72
+ display_message("No evaluations found.", type: :info)
73
+ return
74
+ end
75
+
76
+ display_evaluations_table(evaluations)
77
+ end
78
+
79
+ def run_view_command(args)
80
+ id = args.shift
81
+ unless id
82
+ display_message("Error: Please provide an evaluation ID", type: :error)
83
+ display_message("Usage: aidp eval view <evaluation_id>", type: :info)
84
+ return
85
+ end
86
+
87
+ Aidp.log_debug("eval_command", "view", id: id)
88
+
89
+ record = @storage.load(id)
90
+ unless record
91
+ display_message("Evaluation not found: #{id}", type: :error)
92
+ return
93
+ end
94
+
95
+ display_evaluation_details(record)
96
+ end
97
+
98
+ def run_stats_command
99
+ Aidp.log_debug("eval_command", "stats")
100
+
101
+ stats = @storage.stats
102
+
103
+ display_message("", type: :info)
104
+ display_message("Evaluation Statistics", type: :highlight)
105
+ display_message("=" * 50, type: :muted)
106
+ display_message("", type: :info)
107
+
108
+ display_message("Total evaluations: #{stats[:total]}", type: :info)
109
+ display_message("", type: :info)
110
+
111
+ display_message("By rating:", type: :info)
112
+ display_rating_bar(" Good", stats[:by_rating][:good], stats[:total], :green)
113
+ display_rating_bar(" Neutral", stats[:by_rating][:neutral], stats[:total], :yellow)
114
+ display_rating_bar(" Bad", stats[:by_rating][:bad], stats[:total], :red)
115
+
116
+ if stats[:by_target_type]&.any?
117
+ display_message("", type: :info)
118
+ display_message("By target type:", type: :info)
119
+ stats[:by_target_type].each do |type, count|
120
+ display_message(" #{type || "unspecified"}: #{count}", type: :muted)
121
+ end
122
+ end
123
+
124
+ if stats[:first_evaluation]
125
+ display_message("", type: :info)
126
+ display_message("First evaluation: #{format_timestamp(stats[:first_evaluation])}", type: :muted)
127
+ display_message("Last evaluation: #{format_timestamp(stats[:last_evaluation])}", type: :muted)
128
+ end
129
+
130
+ display_message("", type: :info)
131
+ end
132
+
133
+ def run_add_command(args)
134
+ # Check for watch mode options
135
+ watch_opts = extract_watch_options(args)
136
+
137
+ rating = args.shift
138
+ comment = args.join(" ").strip
139
+ comment = nil if comment.empty?
140
+
141
+ unless rating
142
+ display_message("Error: Please provide a rating (good, neutral, or bad)", type: :error)
143
+ display_message("Usage: aidp eval add <rating> [comment]", type: :info)
144
+ display_message(" aidp eval add --watch <type> <repo> <number> <rating> [comment]", type: :info)
145
+ return
146
+ end
147
+
148
+ Aidp.log_debug("eval_command", "add", rating: rating, has_comment: !comment.nil?, watch: watch_opts)
149
+
150
+ begin
151
+ context_capture = Aidp::Evaluations::ContextCapture.new(project_dir: @project_dir)
152
+
153
+ if watch_opts[:enabled]
154
+ context = context_capture.capture_watch(
155
+ repo: watch_opts[:repo],
156
+ number: watch_opts[:number],
157
+ processor_type: watch_opts[:type]
158
+ )
159
+ target_type = watch_opts[:type]
160
+ target_id = "#{watch_opts[:repo]}##{watch_opts[:number]}"
161
+ else
162
+ context = context_capture.capture_minimal
163
+ target_type = nil
164
+ target_id = nil
165
+ end
166
+
167
+ record = Aidp::Evaluations::EvaluationRecord.new(
168
+ rating: rating,
169
+ comment: comment,
170
+ target_type: target_type,
171
+ target_id: target_id,
172
+ context: context
173
+ )
174
+
175
+ result = @storage.store(record)
176
+
177
+ if result[:success]
178
+ display_message("Evaluation recorded: #{record.id}", type: :success)
179
+ display_message(" Rating: #{rating_with_emoji(record.rating)}", type: :info)
180
+ display_message(" Target: #{target_type} (#{target_id})", type: :info) if target_type
181
+ display_message(" Comment: #{record.comment}", type: :muted) if record.comment
182
+ else
183
+ display_message("Failed to store evaluation: #{result[:error]}", type: :error)
184
+ end
185
+ rescue ArgumentError => e
186
+ display_message("Error: #{e.message}", type: :error)
187
+ end
188
+ end
189
+
190
+ def run_watch_command(args)
191
+ # aidp eval watch <plan|review|build|ci_fix|change_request> <repo> <number> <rating> [comment]
192
+ processor_type = args.shift
193
+ repo = args.shift
194
+ number = args.shift&.to_i
195
+ rating = args.shift
196
+ comment = args.join(" ").strip
197
+ comment = nil if comment.empty?
198
+
199
+ unless processor_type && repo && number && rating
200
+ display_message("Error: Missing required arguments", type: :error)
201
+ display_message("Usage: aidp eval watch <type> <repo> <number> <rating> [comment]", type: :info)
202
+ display_message("", type: :info)
203
+ display_message("Types: plan, review, build, ci_fix, change_request", type: :info)
204
+ display_message("Example: aidp eval watch plan owner/repo 123 good \"Clear plan\"", type: :muted)
205
+ return
206
+ end
207
+
208
+ Aidp.log_debug("eval_command", "watch",
209
+ processor_type: processor_type, repo: repo, number: number, rating: rating)
210
+
211
+ begin
212
+ context_capture = Aidp::Evaluations::ContextCapture.new(project_dir: @project_dir)
213
+ context = context_capture.capture_watch(
214
+ repo: repo,
215
+ number: number,
216
+ processor_type: processor_type
217
+ )
218
+
219
+ record = Aidp::Evaluations::EvaluationRecord.new(
220
+ rating: rating,
221
+ comment: comment,
222
+ target_type: processor_type,
223
+ target_id: "#{repo}##{number}",
224
+ context: context
225
+ )
226
+
227
+ result = @storage.store(record)
228
+
229
+ if result[:success]
230
+ display_message("Watch evaluation recorded: #{record.id}", type: :success)
231
+ display_message(" Rating: #{rating_with_emoji(record.rating)}", type: :info)
232
+ display_message(" Type: #{processor_type}", type: :info)
233
+ display_message(" Target: #{repo}##{number}", type: :info)
234
+ display_message(" Comment: #{record.comment}", type: :muted) if record.comment
235
+ else
236
+ display_message("Failed to store evaluation: #{result[:error]}", type: :error)
237
+ end
238
+ rescue ArgumentError => e
239
+ display_message("Error: #{e.message}", type: :error)
240
+ end
241
+ end
242
+
243
+ def run_clear_command(args)
244
+ force = args.include?("--force")
245
+
246
+ unless force
247
+ confirm = @prompt.yes?("Are you sure you want to clear all evaluation data?")
248
+ return unless confirm
249
+ end
250
+
251
+ Aidp.log_debug("eval_command", "clear", force: force)
252
+
253
+ result = @storage.clear
254
+
255
+ if result[:success]
256
+ display_message("Cleared #{result[:count]} evaluation(s).", type: :success)
257
+ else
258
+ display_message("Failed to clear evaluations: #{result[:error]}", type: :error)
259
+ end
260
+ end
261
+
262
+ def parse_list_options(args)
263
+ options = {limit: 20, rating: nil, target_type: nil}
264
+
265
+ args.each_with_index do |arg, i|
266
+ case arg
267
+ when "--limit", "-n"
268
+ options[:limit] = args[i + 1].to_i if args[i + 1]
269
+ when "--rating", "-r"
270
+ options[:rating] = args[i + 1] if args[i + 1]
271
+ when "--type", "-t"
272
+ options[:target_type] = args[i + 1] if args[i + 1]
273
+ end
274
+ end
275
+
276
+ options
277
+ end
278
+
279
+ def extract_watch_options(args)
280
+ options = {enabled: false, type: nil, repo: nil, number: nil}
281
+
282
+ watch_idx = args.index("--watch")
283
+ return options unless watch_idx
284
+
285
+ # Remove --watch and extract following arguments
286
+ args.delete_at(watch_idx)
287
+
288
+ # Expect: --watch <type> <repo> <number>
289
+ if args[watch_idx] && args[watch_idx + 1] && args[watch_idx + 2]
290
+ options[:enabled] = true
291
+ options[:type] = args.delete_at(watch_idx)
292
+ options[:repo] = args.delete_at(watch_idx)
293
+ options[:number] = args.delete_at(watch_idx).to_i
294
+ end
295
+
296
+ options
297
+ end
298
+
299
+ def display_evaluations_table(evaluations)
300
+ header = ["ID", "Rating", "Target", "Comment", "Created"]
301
+
302
+ rows = evaluations.map do |eval|
303
+ [
304
+ truncate(eval.id, 25),
305
+ rating_with_emoji(eval.rating),
306
+ eval.target_type || "-",
307
+ truncate(eval.comment || "-", 30),
308
+ format_timestamp(eval.created_at)
309
+ ]
310
+ end
311
+
312
+ table = TTY::Table.new(header: header, rows: rows)
313
+ @prompt.say(table.render(:unicode, padding: [0, 1]))
314
+ end
315
+
316
+ def display_evaluation_details(record)
317
+ display_message("", type: :info)
318
+ display_message("Evaluation Details", type: :highlight)
319
+ display_message("=" * 50, type: :muted)
320
+ display_message("", type: :info)
321
+
322
+ display_message("ID: #{record.id}", type: :info)
323
+ display_message("Rating: #{rating_with_emoji(record.rating)}", type: :info)
324
+ display_message("Comment: #{record.comment || "(none)"}", type: :info)
325
+ display_message("Target Type: #{record.target_type || "(none)"}", type: :info)
326
+ display_message("Target ID: #{record.target_id || "(none)"}", type: :info)
327
+ display_message("Created: #{format_timestamp(record.created_at)}", type: :info)
328
+
329
+ if record.context&.any?
330
+ display_message("", type: :info)
331
+ display_message("Context:", type: :highlight)
332
+ display_context(record.context)
333
+ end
334
+
335
+ display_message("", type: :info)
336
+ end
337
+
338
+ def display_context(context, indent: 2)
339
+ prefix = " " * indent
340
+ context.each do |key, value|
341
+ if value.is_a?(Hash)
342
+ display_message("#{prefix}#{key}:", type: :muted)
343
+ display_context(value, indent: indent + 2)
344
+ elsif value.is_a?(Array)
345
+ display_message("#{prefix}#{key}: #{value.join(", ")}", type: :muted)
346
+ else
347
+ display_message("#{prefix}#{key}: #{value}", type: :muted)
348
+ end
349
+ end
350
+ end
351
+
352
+ def display_rating_bar(label, count, total, color)
353
+ percentage = (total > 0) ? (count.to_f / total * 100).round(1) : 0
354
+ bar_width = (total > 0) ? (count.to_f / total * 20).round : 0
355
+ bar = "#" * bar_width + "-" * (20 - bar_width)
356
+
357
+ display_message("#{label}: [#{bar}] #{count} (#{percentage}%)", type: :info)
358
+ end
359
+
360
+ def rating_with_emoji(rating)
361
+ case rating
362
+ when "good" then "good (+)"
363
+ when "neutral" then "neutral (~)"
364
+ when "bad" then "bad (-)"
365
+ else rating
366
+ end
367
+ end
368
+
369
+ def truncate(str, max_length)
370
+ return str if str.nil? || str.length <= max_length
371
+ str[0, max_length - 3] + "..."
372
+ end
373
+
374
+ def format_timestamp(timestamp)
375
+ return "-" unless timestamp
376
+ Time.parse(timestamp).strftime("%Y-%m-%d %H:%M")
377
+ rescue
378
+ timestamp.to_s[0, 16]
379
+ end
380
+
381
+ def display_usage
382
+ display_message("Usage: aidp eval <list|view|stats|add|watch|clear>", type: :info)
383
+ display_message("", type: :info)
384
+ display_message("Commands:", type: :info)
385
+ display_message(" list [options] - List recent evaluations", type: :info)
386
+ display_message(" --limit, -n <N> - Limit results (default: 20)", type: :muted)
387
+ display_message(" --rating, -r <rating> - Filter by rating", type: :muted)
388
+ display_message(" --type, -t <type> - Filter by target type", type: :muted)
389
+ display_message(" view <id> - View evaluation details", type: :info)
390
+ display_message(" stats - Show evaluation statistics", type: :info)
391
+ display_message(" add <rating> [comment] - Add a new evaluation", type: :info)
392
+ display_message(" watch <type> <repo> <number> <rating> [comment]", type: :info)
393
+ display_message(" - Rate a watch mode output", type: :info)
394
+ display_message(" Types: plan, review, build, ci_fix, change_request", type: :muted)
395
+ display_message(" clear [--force] - Clear all evaluation data", type: :info)
396
+ end
397
+ end
398
+ end
399
+ end
@@ -58,7 +58,7 @@ module Aidp
58
58
 
59
59
  # Build a runner to access state manager
60
60
  runner = @runner_class.new(@project_dir, mode.to_sym, {})
61
- state_manager = runner.instance_variable_get(:@state_manager)
61
+ state_manager = runner.state_manager
62
62
  state_manager.reset_all if state_manager.respond_to?(:reset_all)
63
63
  display_message("✅ Reset harness state for #{mode} mode", type: :success)
64
64
  end