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.
- checksums.yaml +4 -4
- data/README.md +194 -25
- data/lib/aidp/analyze/kb_inspector.rb +2 -15
- data/lib/aidp/analyze/progress.rb +2 -1
- data/lib/aidp/analyze/ruby_maat_integration.rb +2 -15
- data/lib/aidp/analyze/runner.rb +64 -20
- data/lib/aidp/analyze/steps.rb +10 -8
- data/lib/aidp/analyze/tree_sitter_grammar_loader.rb +2 -13
- data/lib/aidp/analyze/tree_sitter_scan.rb +2 -13
- data/lib/aidp/cli/checkpoint_command.rb +98 -0
- data/lib/aidp/cli/first_run_wizard.rb +65 -94
- data/lib/aidp/cli/jobs_command.rb +249 -34
- data/lib/aidp/cli.rb +312 -38
- data/lib/aidp/config.rb +5 -8
- data/lib/aidp/debug_logger.rb +4 -4
- data/lib/aidp/debug_mixin.rb +11 -4
- data/lib/aidp/execute/checkpoint.rb +282 -0
- data/lib/aidp/execute/checkpoint_display.rb +221 -0
- data/lib/aidp/execute/progress.rb +2 -1
- data/lib/aidp/execute/prompt_manager.rb +62 -0
- data/lib/aidp/execute/runner.rb +53 -24
- data/lib/aidp/execute/steps.rb +36 -27
- data/lib/aidp/execute/work_loop_runner.rb +308 -0
- data/lib/aidp/execute/workflow_selector.rb +26 -17
- data/lib/aidp/harness/condition_detector.rb +4 -4
- data/lib/aidp/harness/config_schema.rb +40 -0
- data/lib/aidp/harness/config_validator.rb +3 -6
- data/lib/aidp/harness/configuration.rb +35 -1
- data/lib/aidp/harness/enhanced_runner.rb +22 -1
- data/lib/aidp/harness/error_handler.rb +103 -28
- data/lib/aidp/harness/provider_factory.rb +4 -1
- data/lib/aidp/harness/provider_manager.rb +250 -15
- data/lib/aidp/harness/runner.rb +3 -14
- data/lib/aidp/harness/simple_user_interface.rb +2 -15
- data/lib/aidp/harness/status_display.rb +12 -17
- data/lib/aidp/harness/test_runner.rb +83 -0
- data/lib/aidp/harness/ui/enhanced_tui.rb +2 -0
- data/lib/aidp/harness/ui/enhanced_workflow_selector.rb +22 -4
- data/lib/aidp/harness/ui/error_handler.rb +4 -0
- data/lib/aidp/harness/ui/frame_manager.rb +10 -8
- data/lib/aidp/harness/ui/job_monitor.rb +2 -0
- data/lib/aidp/harness/ui/navigation/main_menu.rb +4 -2
- data/lib/aidp/harness/ui/navigation/menu_item.rb +1 -0
- data/lib/aidp/harness/ui/navigation/menu_state.rb +1 -0
- data/lib/aidp/harness/ui/navigation/submenu.rb +1 -0
- data/lib/aidp/harness/ui/navigation/workflow_selector.rb +2 -0
- data/lib/aidp/harness/ui/progress_display.rb +8 -12
- data/lib/aidp/harness/ui/question_collector.rb +2 -0
- data/lib/aidp/harness/ui/spinner_group.rb +2 -0
- data/lib/aidp/harness/ui/spinner_helper.rb +1 -1
- data/lib/aidp/harness/ui/status_manager.rb +4 -2
- data/lib/aidp/harness/ui/status_widget.rb +3 -1
- data/lib/aidp/harness/ui/workflow_controller.rb +2 -0
- data/lib/aidp/harness/user_interface.rb +12 -17
- data/lib/aidp/jobs/background_runner.rb +278 -0
- data/lib/aidp/message_display.rb +48 -0
- data/lib/aidp/provider_manager.rb +3 -1
- data/lib/aidp/providers/anthropic.rb +100 -17
- data/lib/aidp/providers/base.rb +42 -11
- data/lib/aidp/providers/codex.rb +248 -0
- data/lib/aidp/providers/cursor.rb +35 -42
- data/lib/aidp/providers/gemini.rb +25 -15
- data/lib/aidp/providers/github_copilot.rb +41 -42
- data/lib/aidp/providers/opencode.rb +34 -41
- data/lib/aidp/version.rb +1 -1
- data/lib/aidp/workflows/definitions.rb +357 -0
- data/lib/aidp/workflows/selector.rb +171 -0
- data/lib/aidp.rb +12 -0
- data/templates/planning/generate_llm_style_guide.md +119 -0
- metadata +38 -26
- /data/templates/{ANALYZE/02_ARCHITECTURE_ANALYSIS.md → analysis/analyze_architecture.md} +0 -0
- /data/templates/{ANALYZE/05_DOCUMENTATION_ANALYSIS.md → analysis/analyze_documentation.md} +0 -0
- /data/templates/{ANALYZE/04_FUNCTIONALITY_ANALYSIS.md → analysis/analyze_functionality.md} +0 -0
- /data/templates/{ANALYZE/01_REPOSITORY_ANALYSIS.md → analysis/analyze_repository.md} +0 -0
- /data/templates/{ANALYZE/06_STATIC_ANALYSIS.md → analysis/analyze_static_code.md} +0 -0
- /data/templates/{ANALYZE/03_TEST_ANALYSIS.md → analysis/analyze_tests.md} +0 -0
- /data/templates/{ANALYZE/07_REFACTORING_RECOMMENDATIONS.md → analysis/recommend_refactoring.md} +0 -0
- /data/templates/{ANALYZE/06a_tree_sitter_scan.md → analysis/scan_with_tree_sitter.md} +0 -0
- /data/templates/{EXECUTE/11_STATIC_ANALYSIS.md → implementation/configure_static_analysis.md} +0 -0
- /data/templates/{EXECUTE/14_DOCS_PORTAL.md → implementation/create_documentation_portal.md} +0 -0
- /data/templates/{EXECUTE/10_IMPLEMENTATION_AGENT.md → implementation/implement_features.md} +0 -0
- /data/templates/{EXECUTE/13_DELIVERY_ROLLOUT.md → implementation/plan_delivery.md} +0 -0
- /data/templates/{EXECUTE/15_POST_RELEASE.md → implementation/review_post_release.md} +0 -0
- /data/templates/{EXECUTE/09_SCAFFOLDING_DEVEX.md → implementation/setup_scaffolding.md} +0 -0
- /data/templates/{EXECUTE/02A_ARCH_GATE_QUESTIONS.md → planning/ask_architecture_questions.md} +0 -0
- /data/templates/{EXECUTE/00_PRD.md → planning/create_prd.md} +0 -0
- /data/templates/{EXECUTE/08_TASKS.md → planning/create_tasks.md} +0 -0
- /data/templates/{EXECUTE/04_DOMAIN_DECOMPOSITION.md → planning/decompose_domain.md} +0 -0
- /data/templates/{EXECUTE/01_NFRS.md → planning/define_nfrs.md} +0 -0
- /data/templates/{EXECUTE/05_CONTRACTS.md → planning/design_apis.md} +0 -0
- /data/templates/{EXECUTE/02_ARCHITECTURE.md → planning/design_architecture.md} +0 -0
- /data/templates/{EXECUTE/06_THREAT_MODEL.md → planning/design_data_model.md} +0 -0
- /data/templates/{EXECUTE/03_ADR_FACTORY.md → planning/generate_adrs.md} +0 -0
- /data/templates/{EXECUTE/12_OBSERVABILITY_SLOS.md → planning/plan_observability.md} +0 -0
- /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
|
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
|
data/lib/aidp/execute/runner.rb
CHANGED
@@ -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
|
-
|
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
|
-
#
|
65
|
-
|
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",
|
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
|