aidp 0.10.0 → 0.11.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 (95) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +194 -25
  3. data/lib/aidp/analyze/kb_inspector.rb +2 -15
  4. data/lib/aidp/analyze/progress.rb +2 -1
  5. data/lib/aidp/analyze/ruby_maat_integration.rb +2 -15
  6. data/lib/aidp/analyze/runner.rb +64 -20
  7. data/lib/aidp/analyze/steps.rb +10 -8
  8. data/lib/aidp/analyze/tree_sitter_grammar_loader.rb +2 -13
  9. data/lib/aidp/analyze/tree_sitter_scan.rb +2 -13
  10. data/lib/aidp/cli/checkpoint_command.rb +98 -0
  11. data/lib/aidp/cli/first_run_wizard.rb +65 -94
  12. data/lib/aidp/cli/jobs_command.rb +249 -34
  13. data/lib/aidp/cli.rb +312 -38
  14. data/lib/aidp/config.rb +5 -8
  15. data/lib/aidp/debug_logger.rb +4 -4
  16. data/lib/aidp/debug_mixin.rb +11 -4
  17. data/lib/aidp/execute/checkpoint.rb +282 -0
  18. data/lib/aidp/execute/checkpoint_display.rb +221 -0
  19. data/lib/aidp/execute/progress.rb +2 -1
  20. data/lib/aidp/execute/prompt_manager.rb +62 -0
  21. data/lib/aidp/execute/runner.rb +53 -24
  22. data/lib/aidp/execute/steps.rb +36 -27
  23. data/lib/aidp/execute/work_loop_runner.rb +308 -0
  24. data/lib/aidp/execute/workflow_selector.rb +26 -17
  25. data/lib/aidp/harness/condition_detector.rb +4 -4
  26. data/lib/aidp/harness/config_schema.rb +40 -0
  27. data/lib/aidp/harness/config_validator.rb +3 -6
  28. data/lib/aidp/harness/configuration.rb +35 -1
  29. data/lib/aidp/harness/enhanced_runner.rb +22 -1
  30. data/lib/aidp/harness/error_handler.rb +103 -28
  31. data/lib/aidp/harness/provider_factory.rb +4 -1
  32. data/lib/aidp/harness/provider_manager.rb +250 -15
  33. data/lib/aidp/harness/runner.rb +3 -14
  34. data/lib/aidp/harness/simple_user_interface.rb +2 -15
  35. data/lib/aidp/harness/status_display.rb +12 -17
  36. data/lib/aidp/harness/test_runner.rb +83 -0
  37. data/lib/aidp/harness/ui/enhanced_tui.rb +2 -0
  38. data/lib/aidp/harness/ui/enhanced_workflow_selector.rb +22 -4
  39. data/lib/aidp/harness/ui/error_handler.rb +4 -0
  40. data/lib/aidp/harness/ui/frame_manager.rb +10 -8
  41. data/lib/aidp/harness/ui/job_monitor.rb +2 -0
  42. data/lib/aidp/harness/ui/navigation/main_menu.rb +4 -2
  43. data/lib/aidp/harness/ui/navigation/menu_item.rb +1 -0
  44. data/lib/aidp/harness/ui/navigation/menu_state.rb +1 -0
  45. data/lib/aidp/harness/ui/navigation/submenu.rb +1 -0
  46. data/lib/aidp/harness/ui/navigation/workflow_selector.rb +2 -0
  47. data/lib/aidp/harness/ui/progress_display.rb +8 -12
  48. data/lib/aidp/harness/ui/question_collector.rb +2 -0
  49. data/lib/aidp/harness/ui/spinner_group.rb +2 -0
  50. data/lib/aidp/harness/ui/spinner_helper.rb +1 -1
  51. data/lib/aidp/harness/ui/status_manager.rb +4 -2
  52. data/lib/aidp/harness/ui/status_widget.rb +3 -1
  53. data/lib/aidp/harness/ui/workflow_controller.rb +2 -0
  54. data/lib/aidp/harness/user_interface.rb +12 -17
  55. data/lib/aidp/jobs/background_runner.rb +278 -0
  56. data/lib/aidp/message_display.rb +48 -0
  57. data/lib/aidp/provider_manager.rb +3 -1
  58. data/lib/aidp/providers/anthropic.rb +100 -17
  59. data/lib/aidp/providers/base.rb +42 -11
  60. data/lib/aidp/providers/codex.rb +248 -0
  61. data/lib/aidp/providers/cursor.rb +35 -42
  62. data/lib/aidp/providers/gemini.rb +25 -15
  63. data/lib/aidp/providers/github_copilot.rb +41 -42
  64. data/lib/aidp/providers/opencode.rb +34 -41
  65. data/lib/aidp/version.rb +1 -1
  66. data/lib/aidp/workflows/definitions.rb +357 -0
  67. data/lib/aidp/workflows/selector.rb +171 -0
  68. data/lib/aidp.rb +12 -0
  69. data/templates/planning/generate_llm_style_guide.md +119 -0
  70. metadata +38 -26
  71. /data/templates/{ANALYZE/02_ARCHITECTURE_ANALYSIS.md → analysis/analyze_architecture.md} +0 -0
  72. /data/templates/{ANALYZE/05_DOCUMENTATION_ANALYSIS.md → analysis/analyze_documentation.md} +0 -0
  73. /data/templates/{ANALYZE/04_FUNCTIONALITY_ANALYSIS.md → analysis/analyze_functionality.md} +0 -0
  74. /data/templates/{ANALYZE/01_REPOSITORY_ANALYSIS.md → analysis/analyze_repository.md} +0 -0
  75. /data/templates/{ANALYZE/06_STATIC_ANALYSIS.md → analysis/analyze_static_code.md} +0 -0
  76. /data/templates/{ANALYZE/03_TEST_ANALYSIS.md → analysis/analyze_tests.md} +0 -0
  77. /data/templates/{ANALYZE/07_REFACTORING_RECOMMENDATIONS.md → analysis/recommend_refactoring.md} +0 -0
  78. /data/templates/{ANALYZE/06a_tree_sitter_scan.md → analysis/scan_with_tree_sitter.md} +0 -0
  79. /data/templates/{EXECUTE/11_STATIC_ANALYSIS.md → implementation/configure_static_analysis.md} +0 -0
  80. /data/templates/{EXECUTE/14_DOCS_PORTAL.md → implementation/create_documentation_portal.md} +0 -0
  81. /data/templates/{EXECUTE/10_IMPLEMENTATION_AGENT.md → implementation/implement_features.md} +0 -0
  82. /data/templates/{EXECUTE/13_DELIVERY_ROLLOUT.md → implementation/plan_delivery.md} +0 -0
  83. /data/templates/{EXECUTE/15_POST_RELEASE.md → implementation/review_post_release.md} +0 -0
  84. /data/templates/{EXECUTE/09_SCAFFOLDING_DEVEX.md → implementation/setup_scaffolding.md} +0 -0
  85. /data/templates/{EXECUTE/02A_ARCH_GATE_QUESTIONS.md → planning/ask_architecture_questions.md} +0 -0
  86. /data/templates/{EXECUTE/00_PRD.md → planning/create_prd.md} +0 -0
  87. /data/templates/{EXECUTE/08_TASKS.md → planning/create_tasks.md} +0 -0
  88. /data/templates/{EXECUTE/04_DOMAIN_DECOMPOSITION.md → planning/decompose_domain.md} +0 -0
  89. /data/templates/{EXECUTE/01_NFRS.md → planning/define_nfrs.md} +0 -0
  90. /data/templates/{EXECUTE/05_CONTRACTS.md → planning/design_apis.md} +0 -0
  91. /data/templates/{EXECUTE/02_ARCHITECTURE.md → planning/design_architecture.md} +0 -0
  92. /data/templates/{EXECUTE/06_THREAT_MODEL.md → planning/design_data_model.md} +0 -0
  93. /data/templates/{EXECUTE/03_ADR_FACTORY.md → planning/generate_adrs.md} +0 -0
  94. /data/templates/{EXECUTE/12_OBSERVABILITY_SLOS.md → planning/plan_observability.md} +0 -0
  95. /data/templates/{EXECUTE/07_TEST_PLAN.md → planning/plan_testing.md} +0 -0
@@ -0,0 +1,282 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "yaml"
4
+ require "time"
5
+ require "json"
6
+
7
+ module Aidp
8
+ module Execute
9
+ # Manages periodic checkpoints during work loop execution
10
+ # Tracks progress metrics, code quality, and task completion
11
+ class Checkpoint
12
+ attr_reader :project_dir, :checkpoint_file, :history_file
13
+
14
+ def initialize(project_dir)
15
+ @project_dir = project_dir
16
+ @checkpoint_file = File.join(project_dir, ".aidp", "checkpoint.yml")
17
+ @history_file = File.join(project_dir, ".aidp", "checkpoint_history.jsonl")
18
+ ensure_checkpoint_directory
19
+ end
20
+
21
+ # Record a checkpoint during work loop iteration
22
+ def record_checkpoint(step_name, iteration, metrics = {})
23
+ checkpoint_data = {
24
+ step_name: step_name,
25
+ iteration: iteration,
26
+ timestamp: Time.now.iso8601,
27
+ metrics: collect_metrics.merge(metrics),
28
+ status: determine_status(metrics)
29
+ }
30
+
31
+ save_checkpoint(checkpoint_data)
32
+ append_to_history(checkpoint_data)
33
+
34
+ checkpoint_data
35
+ end
36
+
37
+ # Get the latest checkpoint data
38
+ def latest_checkpoint
39
+ return nil unless File.exist?(@checkpoint_file)
40
+ YAML.load_file(@checkpoint_file)
41
+ end
42
+
43
+ # Get checkpoint history for analysis
44
+ def checkpoint_history(limit: 100)
45
+ return [] unless File.exist?(@history_file)
46
+
47
+ File.readlines(@history_file).last(limit).map do |line|
48
+ JSON.parse(line, symbolize_names: true)
49
+ end
50
+ rescue JSON::ParserError
51
+ []
52
+ end
53
+
54
+ # Get progress summary comparing current state to previous checkpoints
55
+ def progress_summary
56
+ latest = latest_checkpoint
57
+ return nil unless latest
58
+
59
+ history = checkpoint_history(limit: 10)
60
+ previous = history[-2] if history.size > 1
61
+
62
+ {
63
+ current: latest,
64
+ previous: previous,
65
+ trends: calculate_trends(history),
66
+ quality_score: calculate_quality_score(latest[:metrics])
67
+ }
68
+ end
69
+
70
+ # Clear all checkpoint data
71
+ def clear
72
+ File.delete(@checkpoint_file) if File.exist?(@checkpoint_file)
73
+ File.delete(@history_file) if File.exist?(@history_file)
74
+ end
75
+
76
+ private
77
+
78
+ def ensure_checkpoint_directory
79
+ dir = File.dirname(@checkpoint_file)
80
+ FileUtils.mkdir_p(dir) unless File.exist?(dir)
81
+ end
82
+
83
+ # Collect current metrics from the project
84
+ def collect_metrics
85
+ {
86
+ lines_of_code: count_lines_of_code,
87
+ file_count: count_project_files,
88
+ test_coverage: estimate_test_coverage,
89
+ code_quality: assess_code_quality,
90
+ prd_task_progress: calculate_prd_task_progress
91
+ }
92
+ end
93
+
94
+ def count_lines_of_code
95
+ extensions = %w[.rb .js .py .java .go .ts .tsx .jsx]
96
+ total_lines = 0
97
+
98
+ Dir.glob(File.join(@project_dir, "**", "*")).each do |file|
99
+ next unless File.file?(file)
100
+ next unless extensions.include?(File.extname(file))
101
+ next if file.include?("node_modules") || file.include?("vendor")
102
+
103
+ total_lines += File.readlines(file).size
104
+ end
105
+
106
+ total_lines
107
+ rescue
108
+ 0
109
+ end
110
+
111
+ def count_project_files
112
+ extensions = %w[.rb .js .py .java .go .ts .tsx .jsx]
113
+ count = 0
114
+
115
+ Dir.glob(File.join(@project_dir, "**", "*")).each do |file|
116
+ next unless File.file?(file)
117
+ next unless extensions.include?(File.extname(file))
118
+ next if file.include?("node_modules") || file.include?("vendor")
119
+
120
+ count += 1
121
+ end
122
+
123
+ count
124
+ rescue
125
+ 0
126
+ end
127
+
128
+ # Estimate test coverage based on test files vs source files
129
+ def estimate_test_coverage
130
+ source_files = count_files_by_pattern("**/*.rb", exclude: ["spec/**", "test/**"])
131
+ test_files = count_files_by_pattern("{spec,test}/**/*_{spec,test}.rb")
132
+
133
+ return 0 if source_files == 0
134
+
135
+ coverage_ratio = (test_files.to_f / source_files * 100).round(2)
136
+ [coverage_ratio, 100].min
137
+ rescue
138
+ 0
139
+ end
140
+
141
+ def count_files_by_pattern(pattern, exclude: [])
142
+ files = Dir.glob(File.join(@project_dir, pattern))
143
+ exclude.each do |exc_pattern|
144
+ excluded = Dir.glob(File.join(@project_dir, exc_pattern))
145
+ files -= excluded
146
+ end
147
+ files.size
148
+ end
149
+
150
+ # Assess code quality based on available linters
151
+ def assess_code_quality
152
+ quality_score = 100
153
+
154
+ # Check for Ruby linter output
155
+ if File.exist?(File.join(@project_dir, ".rubocop.yml"))
156
+ rubocop_score = run_rubocop_check
157
+ quality_score = [quality_score, rubocop_score].min if rubocop_score
158
+ end
159
+
160
+ quality_score
161
+ end
162
+
163
+ def run_rubocop_check
164
+ # Run rubocop and parse output to get a quality score
165
+ # This is a simplified version - could be enhanced
166
+ result = `cd #{@project_dir} && rubocop --format json 2>/dev/null`
167
+ return nil if result.empty?
168
+
169
+ data = JSON.parse(result)
170
+ total_files = data["files"]&.size || 0
171
+ return 100 if total_files == 0
172
+
173
+ offense_count = data["summary"]["offense_count"] || 0
174
+ # Simple scoring: fewer offenses = higher score
175
+ [100 - (offense_count / total_files.to_f * 10), 0].max.round(2)
176
+ rescue
177
+ nil
178
+ end
179
+
180
+ # Calculate PRD task completion progress
181
+ def calculate_prd_task_progress
182
+ prd_path = File.join(@project_dir, "docs", "prd.md")
183
+ return 0 unless File.exist?(prd_path)
184
+
185
+ content = File.read(prd_path)
186
+
187
+ # Count completed vs total checkboxes
188
+ total_tasks = content.scan(/- \[[ x]\]/).size
189
+ completed_tasks = content.scan(/- \[x\]/i).size
190
+
191
+ return 0 if total_tasks == 0
192
+
193
+ (completed_tasks.to_f / total_tasks * 100).round(2)
194
+ rescue
195
+ 0
196
+ end
197
+
198
+ def determine_status(metrics)
199
+ # Determine overall status based on metrics
200
+ quality_score = calculate_quality_score(metrics)
201
+
202
+ if quality_score >= 80
203
+ "healthy"
204
+ elsif quality_score >= 60
205
+ "warning"
206
+ else
207
+ "needs_attention"
208
+ end
209
+ end
210
+
211
+ def calculate_quality_score(metrics)
212
+ # Weighted average of different metrics
213
+ weights = {
214
+ test_coverage: 0.3,
215
+ code_quality: 0.4,
216
+ prd_task_progress: 0.3
217
+ }
218
+
219
+ score = 0
220
+ weights.each do |metric, weight|
221
+ score += (metrics[metric] || 0) * weight
222
+ end
223
+
224
+ score.round(2)
225
+ end
226
+
227
+ # Calculate trends from historical data
228
+ def calculate_trends(history)
229
+ return {} if history.size < 2
230
+
231
+ latest = history.last
232
+ previous = history[-2]
233
+
234
+ {
235
+ lines_of_code: calculate_trend_direction(
236
+ previous.dig(:metrics, :lines_of_code),
237
+ latest.dig(:metrics, :lines_of_code)
238
+ ),
239
+ test_coverage: calculate_trend_direction(
240
+ previous.dig(:metrics, :test_coverage),
241
+ latest.dig(:metrics, :test_coverage)
242
+ ),
243
+ code_quality: calculate_trend_direction(
244
+ previous.dig(:metrics, :code_quality),
245
+ latest.dig(:metrics, :code_quality)
246
+ ),
247
+ prd_task_progress: calculate_trend_direction(
248
+ previous.dig(:metrics, :prd_task_progress),
249
+ latest.dig(:metrics, :prd_task_progress)
250
+ )
251
+ }
252
+ end
253
+
254
+ def calculate_trend_direction(previous_value, current_value)
255
+ return "stable" if previous_value.nil? || current_value.nil?
256
+
257
+ diff = current_value - previous_value
258
+ diff_percent = (previous_value == 0) ? 0 : ((diff.to_f / previous_value) * 100).round(2)
259
+
260
+ {
261
+ direction: if diff > 0
262
+ "up"
263
+ else
264
+ ((diff < 0) ? "down" : "stable")
265
+ end,
266
+ change: diff,
267
+ change_percent: diff_percent
268
+ }
269
+ end
270
+
271
+ def save_checkpoint(data)
272
+ File.write(@checkpoint_file, data.to_yaml)
273
+ end
274
+
275
+ def append_to_history(data)
276
+ File.open(@history_file, "a") do |f|
277
+ f.puts(data.to_json)
278
+ end
279
+ end
280
+ end
281
+ end
282
+ end
@@ -0,0 +1,221 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "pastel"
4
+
5
+ module Aidp
6
+ module Execute
7
+ # Formats and displays checkpoint information to the user
8
+ class CheckpointDisplay
9
+ include Aidp::MessageDisplay
10
+
11
+ def initialize
12
+ @pastel = Pastel.new
13
+ end
14
+
15
+ # Display a checkpoint during work loop iteration
16
+ def display_checkpoint(checkpoint_data, show_details: false)
17
+ return unless checkpoint_data
18
+
19
+ puts
20
+ puts @pastel.bold("📊 Checkpoint - Iteration #{checkpoint_data[:iteration]}")
21
+ puts @pastel.dim("─" * 60)
22
+
23
+ display_metrics(checkpoint_data[:metrics])
24
+ display_status(checkpoint_data[:status])
25
+
26
+ if show_details
27
+ display_trends(checkpoint_data[:trends]) if checkpoint_data[:trends]
28
+ end
29
+
30
+ puts @pastel.dim("─" * 60)
31
+ puts
32
+ end
33
+
34
+ # Display progress summary with trends
35
+ def display_progress_summary(summary)
36
+ return unless summary
37
+
38
+ puts
39
+ puts @pastel.bold("📈 Progress Summary")
40
+ puts @pastel.dim("=" * 60)
41
+
42
+ current = summary[:current]
43
+ puts "Step: #{@pastel.cyan(current[:step_name])}"
44
+ puts "Iteration: #{current[:iteration]}"
45
+ puts "Status: #{format_status(current[:status])}"
46
+ puts
47
+
48
+ puts @pastel.bold("Current Metrics:")
49
+ display_metrics(current[:metrics])
50
+
51
+ if summary[:trends]
52
+ puts
53
+ puts @pastel.bold("Trends:")
54
+ display_trends(summary[:trends])
55
+ end
56
+
57
+ if summary[:quality_score]
58
+ puts
59
+ display_quality_score(summary[:quality_score])
60
+ end
61
+
62
+ puts @pastel.dim("=" * 60)
63
+ puts
64
+ end
65
+
66
+ # Display checkpoint history as a table
67
+ def display_checkpoint_history(history, limit: 10)
68
+ return if history.empty?
69
+
70
+ puts
71
+ puts @pastel.bold("📜 Checkpoint History (Last #{[limit, history.size].min})")
72
+ puts @pastel.dim("=" * 80)
73
+
74
+ # Table header
75
+ puts format_table_row([
76
+ "Iteration",
77
+ "Time",
78
+ "LOC",
79
+ "Coverage",
80
+ "Quality",
81
+ "PRD Progress",
82
+ "Status"
83
+ ], header: true)
84
+ puts @pastel.dim("-" * 80)
85
+
86
+ # Table rows
87
+ history.last(limit).each do |checkpoint|
88
+ metrics = checkpoint[:metrics]
89
+ timestamp = Time.parse(checkpoint[:timestamp]).strftime("%H:%M:%S")
90
+
91
+ puts format_table_row([
92
+ checkpoint[:iteration].to_s,
93
+ timestamp,
94
+ metrics[:lines_of_code].to_s,
95
+ "#{metrics[:test_coverage]}%",
96
+ "#{metrics[:code_quality]}%",
97
+ "#{metrics[:prd_task_progress]}%",
98
+ format_status(checkpoint[:status])
99
+ ])
100
+ end
101
+
102
+ puts @pastel.dim("=" * 80)
103
+ puts
104
+ end
105
+
106
+ # Display inline progress indicator (for work loop)
107
+ def display_inline_progress(iteration, metrics)
108
+ loc = metrics[:lines_of_code] || 0
109
+ coverage = metrics[:test_coverage] || 0
110
+ quality = metrics[:code_quality] || 0
111
+ prd = metrics[:prd_task_progress] || 0
112
+
113
+ status_line = [
114
+ "Iter: #{iteration}",
115
+ "LOC: #{loc}",
116
+ "Cov: #{format_percentage(coverage)}",
117
+ "Qual: #{format_percentage(quality)}",
118
+ "PRD: #{format_percentage(prd)}"
119
+ ].join(" | ")
120
+
121
+ display_message(" #{@pastel.dim(status_line)}", type: :info)
122
+ end
123
+
124
+ private
125
+
126
+ def display_metrics(metrics)
127
+ puts " Lines of Code: #{@pastel.yellow(metrics[:lines_of_code].to_s)}"
128
+ puts " Test Coverage: #{format_percentage_with_color(metrics[:test_coverage])}"
129
+ puts " Code Quality: #{format_percentage_with_color(metrics[:code_quality])}"
130
+ puts " PRD Task Progress: #{format_percentage_with_color(metrics[:prd_task_progress])}"
131
+ puts " File Count: #{metrics[:file_count]}"
132
+ end
133
+
134
+ def display_status(status)
135
+ puts " Overall Status: #{format_status(status)}"
136
+ end
137
+
138
+ def format_status(status)
139
+ case status.to_s
140
+ when "healthy"
141
+ @pastel.green("✓ Healthy")
142
+ when "warning"
143
+ @pastel.yellow("⚠ Warning")
144
+ when "needs_attention"
145
+ @pastel.red("✗ Needs Attention")
146
+ else
147
+ @pastel.dim(status.to_s)
148
+ end
149
+ end
150
+
151
+ def display_trends(trends)
152
+ trends.each do |metric, trend_data|
153
+ next unless trend_data.is_a?(Hash)
154
+
155
+ metric_name = metric.to_s.split("_").map(&:capitalize).join(" ")
156
+ arrow = trend_arrow(trend_data[:direction])
157
+ change = format_change(trend_data[:change], trend_data[:change_percent])
158
+
159
+ puts " #{metric_name}: #{arrow} #{change}"
160
+ end
161
+ end
162
+
163
+ def trend_arrow(direction)
164
+ case direction.to_s
165
+ when "up"
166
+ @pastel.green("↑")
167
+ when "down"
168
+ @pastel.red("↓")
169
+ else
170
+ @pastel.dim("→")
171
+ end
172
+ end
173
+
174
+ def format_change(change, change_percent)
175
+ sign = (change >= 0) ? "+" : ""
176
+ "#{sign}#{change} (#{sign}#{change_percent}%)"
177
+ end
178
+
179
+ def display_quality_score(score)
180
+ color = if score >= 80
181
+ :green
182
+ elsif score >= 60
183
+ :yellow
184
+ else
185
+ :red
186
+ end
187
+
188
+ puts " Quality Score: #{@pastel.send(color, "#{score.round(2)}%")}"
189
+ end
190
+
191
+ def format_percentage(value)
192
+ return "0%" unless value
193
+ "#{value.round(1)}%"
194
+ end
195
+
196
+ def format_percentage_with_color(value)
197
+ return @pastel.dim("0%") unless value
198
+
199
+ color = if value >= 80
200
+ :green
201
+ elsif value >= 60
202
+ :yellow
203
+ else
204
+ :red
205
+ end
206
+
207
+ @pastel.send(color, "#{value.round(1)}%")
208
+ end
209
+
210
+ def format_table_row(columns, header: false)
211
+ widths = [10, 10, 8, 10, 10, 14, 18]
212
+ formatted = columns.each_with_index.map do |col, i|
213
+ col.to_s.ljust(widths[i])
214
+ end
215
+
216
+ row = formatted.join(" ")
217
+ header ? @pastel.bold(row) : row
218
+ end
219
+ end
220
+ end
221
+ end
@@ -11,7 +11,7 @@ module Aidp
11
11
 
12
12
  def initialize(project_dir)
13
13
  @project_dir = project_dir
14
- @progress_file = File.join(project_dir, ".aidp-progress.yml")
14
+ @progress_file = File.join(project_dir, ".aidp", "progress", "execute.yml")
15
15
  load_progress
16
16
  end
17
17
 
@@ -78,6 +78,7 @@ module Aidp
78
78
  # In test mode, skip file operations to avoid hanging
79
79
  return if ENV["RACK_ENV"] == "test" || defined?(RSpec)
80
80
 
81
+ FileUtils.mkdir_p(File.dirname(@progress_file))
81
82
  File.write(@progress_file, @progress.to_yaml)
82
83
  end
83
84
  end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fileutils"
4
+
5
+ module Aidp
6
+ module Execute
7
+ # Manages the PROMPT.md file lifecycle for work loops
8
+ # Responsibilities:
9
+ # - Read/write PROMPT.md
10
+ # - Check existence
11
+ # - Archive completed prompts
12
+ class PromptManager
13
+ PROMPT_FILENAME = "PROMPT.md"
14
+ ARCHIVE_DIR = ".aidp/prompt_archive"
15
+
16
+ def initialize(project_dir)
17
+ @project_dir = project_dir
18
+ @prompt_path = File.join(project_dir, PROMPT_FILENAME)
19
+ @archive_dir = File.join(project_dir, ARCHIVE_DIR)
20
+ end
21
+
22
+ # Write content to PROMPT.md
23
+ def write(content)
24
+ File.write(@prompt_path, content)
25
+ end
26
+
27
+ # Read content from PROMPT.md
28
+ def read
29
+ return nil unless exists?
30
+ File.read(@prompt_path)
31
+ end
32
+
33
+ # Check if PROMPT.md exists
34
+ def exists?
35
+ File.exist?(@prompt_path)
36
+ end
37
+
38
+ # Archive PROMPT.md with timestamp and step name
39
+ def archive(step_name)
40
+ return unless exists?
41
+
42
+ FileUtils.mkdir_p(@archive_dir)
43
+ timestamp = Time.now.strftime("%Y%m%d_%H%M%S")
44
+ archive_filename = "#{timestamp}_#{step_name}_PROMPT.md"
45
+ archive_path = File.join(@archive_dir, archive_filename)
46
+
47
+ FileUtils.cp(@prompt_path, archive_path)
48
+ archive_path
49
+ end
50
+
51
+ # Delete PROMPT.md (typically after archiving)
52
+ def delete
53
+ File.delete(@prompt_path) if exists?
54
+ end
55
+
56
+ # Get the full path to PROMPT.md
57
+ def path
58
+ @prompt_path
59
+ end
60
+ end
61
+ end
62
+ end
@@ -3,11 +3,14 @@
3
3
  require "tty-prompt"
4
4
  require_relative "steps"
5
5
  require_relative "progress"
6
+ require_relative "work_loop_runner"
6
7
  require_relative "../storage/file_manager"
7
8
 
8
9
  module Aidp
9
10
  module Execute
10
11
  class Runner
12
+ include Aidp::MessageDisplay
13
+
11
14
  def initialize(project_dir, harness_runner = nil, prompt: TTY::Prompt.new)
12
15
  @project_dir = project_dir
13
16
  @harness_runner = harness_runner
@@ -22,18 +25,6 @@ module Aidp
22
25
 
23
26
  private
24
27
 
25
- def display_message(message, type: :info)
26
- color = case type
27
- when :error then :red
28
- when :success then :green
29
- when :warning then :yellow
30
- when :info then :blue
31
- when :highlight then :cyan
32
- else :white
33
- end
34
- @prompt.say(message, color: color)
35
- end
36
-
37
28
  public
38
29
 
39
30
  def run_step(step_name, options = {})
@@ -51,18 +42,17 @@ module Aidp
51
42
 
52
43
  # Harness-aware step execution
53
44
  def run_step_with_harness(step_name, options = {})
54
- # Get current provider from harness
55
- current_provider = @harness_runner.current_provider
56
- provider_type = current_provider || "cursor"
57
-
58
- # Compose prompt with harness context
59
- prompt = composed_prompt_with_harness_context(step_name, options)
60
-
61
- # Execute with harness error handling
62
- result = execute_with_harness_provider(provider_type, prompt, step_name, options)
45
+ step_spec = Aidp::Execute::Steps::SPEC[step_name]
63
46
 
64
- # Process result for harness
65
- process_result_for_harness(result, step_name, options)
47
+ # Check if work loops are enabled in configuration
48
+ config = @harness_runner.instance_variable_get(:@configuration)
49
+ if config&.work_loop_enabled?
50
+ # Use WorkLoopRunner for execution
51
+ run_step_with_work_loop(step_name, step_spec, options)
52
+ else
53
+ # Use traditional single-pass execution
54
+ run_step_traditional(step_name, step_spec, options)
55
+ end
66
56
  end
67
57
 
68
58
  # Standalone step execution (simplified - synchronous)
@@ -154,6 +144,42 @@ module Aidp
154
144
 
155
145
  private
156
146
 
147
+ # Execute step with work loop
148
+ def run_step_with_work_loop(step_name, step_spec, options)
149
+ config = @harness_runner.instance_variable_get(:@configuration)
150
+ provider_manager = @harness_runner.provider_manager
151
+
152
+ # Create work loop runner
153
+ work_loop_runner = WorkLoopRunner.new(
154
+ @project_dir,
155
+ provider_manager,
156
+ config,
157
+ options
158
+ )
159
+
160
+ # Execute with work loop
161
+ result = work_loop_runner.execute_step(step_name, step_spec, options)
162
+
163
+ # Process result for harness
164
+ process_result_for_harness(result, step_name, options)
165
+ end
166
+
167
+ # Execute step traditionally (single pass)
168
+ def run_step_traditional(step_name, step_spec, options)
169
+ # Get current provider from harness
170
+ current_provider = @harness_runner.current_provider
171
+ provider_type = current_provider || "cursor"
172
+
173
+ # Compose prompt with harness context
174
+ prompt = composed_prompt_with_harness_context(step_name, options)
175
+
176
+ # Execute with harness error handling
177
+ result = execute_with_harness_provider(provider_type, prompt, step_name, options)
178
+
179
+ # Process result for harness
180
+ process_result_for_harness(result, step_name, options)
181
+ end
182
+
157
183
  # Simple synchronous step execution
158
184
  def execute_step_synchronously(step_name, prompt, options)
159
185
  # Execute step synchronously with provider
@@ -280,7 +306,10 @@ module Aidp
280
306
 
281
307
  def template_search_paths
282
308
  [
283
- File.join(@project_dir, "templates", "EXECUTE"),
309
+ File.join(@project_dir, "templates"), # Root templates folder
310
+ File.join(@project_dir, "templates", "planning"),
311
+ File.join(@project_dir, "templates", "analysis"),
312
+ File.join(@project_dir, "templates", "implementation"),
284
313
  File.join(@project_dir, "templates", "COMMON")
285
314
  ]
286
315
  end