aidp 0.9.6 → 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/error_handler.rb +4 -2
- data/lib/aidp/{analysis → analyze}/kb_inspector.rb +93 -89
- data/lib/aidp/analyze/prioritizer.rb +3 -2
- data/lib/aidp/analyze/progress.rb +2 -1
- data/lib/aidp/analyze/ruby_maat_integration.rb +7 -3
- data/lib/aidp/analyze/runner.rb +73 -11
- data/lib/aidp/{analysis → analyze}/seams.rb +1 -1
- data/lib/aidp/analyze/steps.rb +10 -8
- data/lib/aidp/{analysis → analyze}/tree_sitter_grammar_loader.rb +11 -5
- data/lib/aidp/{analysis → analyze}/tree_sitter_scan.rb +21 -15
- data/lib/aidp/cli/checkpoint_command.rb +98 -0
- data/lib/aidp/cli/first_run_wizard.rb +83 -103
- data/lib/aidp/cli/jobs_command.rb +270 -36
- data/lib/aidp/cli/terminal_io.rb +3 -3
- data/lib/aidp/cli.rb +411 -69
- 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 +67 -20
- 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 +50 -26
- 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 +25 -4
- data/lib/aidp/harness/error_handler.rb +103 -28
- data/lib/aidp/harness/provider_factory.rb +6 -1
- data/lib/aidp/harness/provider_manager.rb +273 -19
- data/lib/aidp/harness/runner.rb +14 -6
- data/lib/aidp/harness/simple_user_interface.rb +6 -4
- data/lib/aidp/harness/status_display.rb +118 -106
- data/lib/aidp/harness/test_runner.rb +83 -0
- data/lib/aidp/harness/ui/enhanced_tui.rb +7 -5
- data/lib/aidp/harness/ui/enhanced_workflow_selector.rb +22 -4
- data/lib/aidp/harness/ui/error_handler.rb +7 -2
- data/lib/aidp/harness/ui/frame_manager.rb +61 -39
- data/lib/aidp/harness/ui/job_monitor.rb +2 -0
- data/lib/aidp/harness/ui/navigation/main_menu.rb +27 -16
- 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 +26 -7
- 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 +20 -9
- data/lib/aidp/harness/ui/workflow_controller.rb +27 -9
- data/lib/aidp/harness/user_interface.rb +338 -330
- data/lib/aidp/jobs/background_runner.rb +278 -0
- data/lib/aidp/message_display.rb +48 -0
- data/lib/aidp/provider_manager.rb +13 -7
- data/lib/aidp/providers/anthropic.rb +101 -18
- data/lib/aidp/providers/base.rb +51 -1
- data/lib/aidp/providers/codex.rb +248 -0
- data/lib/aidp/providers/cursor.rb +39 -48
- data/lib/aidp/providers/gemini.rb +26 -16
- data/lib/aidp/providers/github_copilot.rb +263 -0
- data/lib/aidp/providers/opencode.rb +38 -47
- 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 +16 -4
- data/templates/planning/generate_llm_style_guide.md +119 -0
- metadata +43 -31
- data/lib/aidp/analyze/progress_visualizer.rb +0 -314
- /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
data/lib/aidp/execute/runner.rb
CHANGED
|
@@ -1,23 +1,32 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "tty-prompt"
|
|
3
4
|
require_relative "steps"
|
|
4
5
|
require_relative "progress"
|
|
6
|
+
require_relative "work_loop_runner"
|
|
5
7
|
require_relative "../storage/file_manager"
|
|
6
8
|
|
|
7
9
|
module Aidp
|
|
8
10
|
module Execute
|
|
9
11
|
class Runner
|
|
10
|
-
|
|
12
|
+
include Aidp::MessageDisplay
|
|
13
|
+
|
|
14
|
+
def initialize(project_dir, harness_runner = nil, prompt: TTY::Prompt.new)
|
|
11
15
|
@project_dir = project_dir
|
|
12
16
|
@harness_runner = harness_runner
|
|
13
17
|
@is_harness_mode = !harness_runner.nil?
|
|
14
18
|
@file_manager = Aidp::Storage::FileManager.new(File.join(project_dir, ".aidp"))
|
|
19
|
+
@prompt = prompt
|
|
15
20
|
end
|
|
16
21
|
|
|
17
22
|
def progress
|
|
18
23
|
@progress ||= Aidp::Execute::Progress.new(@project_dir)
|
|
19
24
|
end
|
|
20
25
|
|
|
26
|
+
private
|
|
27
|
+
|
|
28
|
+
public
|
|
29
|
+
|
|
21
30
|
def run_step(step_name, options = {})
|
|
22
31
|
# Always validate step exists first
|
|
23
32
|
step_spec = Aidp::Execute::Steps::SPEC[step_name]
|
|
@@ -33,23 +42,22 @@ module Aidp
|
|
|
33
42
|
|
|
34
43
|
# Harness-aware step execution
|
|
35
44
|
def run_step_with_harness(step_name, options = {})
|
|
36
|
-
|
|
37
|
-
current_provider = @harness_runner.instance_variable_get(:@current_provider)
|
|
38
|
-
provider_type = current_provider || "cursor"
|
|
39
|
-
|
|
40
|
-
# Compose prompt with harness context
|
|
41
|
-
prompt = composed_prompt_with_harness_context(step_name, options)
|
|
42
|
-
|
|
43
|
-
# Execute with harness error handling
|
|
44
|
-
result = execute_with_harness_provider(provider_type, prompt, step_name, options)
|
|
45
|
+
step_spec = Aidp::Execute::Steps::SPEC[step_name]
|
|
45
46
|
|
|
46
|
-
#
|
|
47
|
-
|
|
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
|
|
48
56
|
end
|
|
49
57
|
|
|
50
58
|
# Standalone step execution (simplified - synchronous)
|
|
51
59
|
def run_step_standalone(step_name, options = {})
|
|
52
|
-
|
|
60
|
+
display_message("🚀 Running execution step synchronously: #{step_name}", type: :info)
|
|
53
61
|
|
|
54
62
|
start_time = Time.now
|
|
55
63
|
prompt = composed_prompt(step_name, options)
|
|
@@ -62,7 +70,7 @@ module Aidp
|
|
|
62
70
|
# Store execution metrics
|
|
63
71
|
@file_manager.record_step_execution(step_name, "cursor", duration, result[:status] == "completed")
|
|
64
72
|
|
|
65
|
-
|
|
73
|
+
display_message("✅ Execution step completed in #{duration.round(2)}s", type: :success)
|
|
66
74
|
result
|
|
67
75
|
end
|
|
68
76
|
|
|
@@ -136,6 +144,42 @@ module Aidp
|
|
|
136
144
|
|
|
137
145
|
private
|
|
138
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
|
+
|
|
139
183
|
# Simple synchronous step execution
|
|
140
184
|
def execute_step_synchronously(step_name, prompt, options)
|
|
141
185
|
# Execute step synchronously with provider
|
|
@@ -172,11 +216,11 @@ module Aidp
|
|
|
172
216
|
# Add current execution context
|
|
173
217
|
context_parts << "## Execution Context"
|
|
174
218
|
context_parts << "Project Directory: #{@project_dir}"
|
|
175
|
-
context_parts << "Current Step: #{@harness_runner.
|
|
176
|
-
context_parts << "Current Provider: #{@harness_runner.
|
|
219
|
+
context_parts << "Current Step: #{@harness_runner.current_step}"
|
|
220
|
+
context_parts << "Current Provider: #{@harness_runner.current_provider}"
|
|
177
221
|
|
|
178
222
|
# Add user input context
|
|
179
|
-
user_input = @harness_runner.
|
|
223
|
+
user_input = @harness_runner.user_input
|
|
180
224
|
if user_input && !user_input.empty?
|
|
181
225
|
context_parts << "\n## Previous User Input"
|
|
182
226
|
user_input.each do |key, value|
|
|
@@ -185,7 +229,7 @@ module Aidp
|
|
|
185
229
|
end
|
|
186
230
|
|
|
187
231
|
# Add execution history context
|
|
188
|
-
execution_log = @harness_runner.
|
|
232
|
+
execution_log = @harness_runner.execution_log
|
|
189
233
|
if execution_log && !execution_log.empty?
|
|
190
234
|
context_parts << "\n## Execution History"
|
|
191
235
|
recent_logs = execution_log.last(5) # Last 5 entries
|
|
@@ -200,7 +244,7 @@ module Aidp
|
|
|
200
244
|
# Execute step with harness provider management
|
|
201
245
|
def execute_with_harness_provider(provider_type, prompt, step_name, _options)
|
|
202
246
|
# Get provider manager from harness
|
|
203
|
-
provider_manager = @harness_runner.
|
|
247
|
+
provider_manager = @harness_runner.provider_manager
|
|
204
248
|
|
|
205
249
|
# Execute with provider
|
|
206
250
|
provider_manager.execute_with_provider(provider_type, prompt, {
|
|
@@ -262,7 +306,10 @@ module Aidp
|
|
|
262
306
|
|
|
263
307
|
def template_search_paths
|
|
264
308
|
[
|
|
265
|
-
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"),
|
|
266
313
|
File.join(@project_dir, "templates", "COMMON")
|
|
267
314
|
]
|
|
268
315
|
end
|
data/lib/aidp/execute/steps.rb
CHANGED
|
@@ -4,117 +4,126 @@ module Aidp
|
|
|
4
4
|
module Execute
|
|
5
5
|
module Steps
|
|
6
6
|
# Simplified step specifications with fewer gates
|
|
7
|
+
# Templates are now organized by purpose (planning/, analysis/, implementation/)
|
|
8
|
+
# and named with action verbs for clarity
|
|
7
9
|
SPEC = {
|
|
10
|
+
"00_LLM_STYLE_GUIDE" => {
|
|
11
|
+
"templates" => ["planning/generate_llm_style_guide.md"],
|
|
12
|
+
"description" => "Generate project-specific LLM Style Guide",
|
|
13
|
+
"outs" => ["docs/LLM_STYLE_GUIDE.md"],
|
|
14
|
+
"gate" => false,
|
|
15
|
+
"interactive" => false
|
|
16
|
+
},
|
|
8
17
|
"00_PRD" => {
|
|
9
|
-
"templates" => ["
|
|
18
|
+
"templates" => ["planning/create_prd.md"],
|
|
10
19
|
"description" => "Generate Product Requirements Document",
|
|
11
20
|
"outs" => ["docs/prd.md"],
|
|
12
|
-
"gate" => false,
|
|
13
|
-
"interactive" => true
|
|
21
|
+
"gate" => false, # Now auto-generated from user input
|
|
22
|
+
"interactive" => true # Uses collected user input
|
|
14
23
|
},
|
|
15
24
|
"01_NFRS" => {
|
|
16
|
-
"templates" => ["
|
|
25
|
+
"templates" => ["planning/define_nfrs.md"],
|
|
17
26
|
"description" => "Define Non-Functional Requirements",
|
|
18
27
|
"outs" => ["docs/nfrs.md"],
|
|
19
|
-
"gate" => false
|
|
28
|
+
"gate" => false # Auto-generated
|
|
20
29
|
},
|
|
21
30
|
"02_ARCHITECTURE" => {
|
|
22
|
-
"templates" => ["
|
|
31
|
+
"templates" => ["planning/design_architecture.md"],
|
|
23
32
|
"description" => "Design System Architecture",
|
|
24
33
|
"outs" => ["docs/architecture.md"],
|
|
25
|
-
"gate" => false
|
|
34
|
+
"gate" => false # Auto-generated
|
|
26
35
|
},
|
|
27
36
|
"02A_ARCH_GATE_QUESTIONS" => {
|
|
28
|
-
"templates" => ["
|
|
37
|
+
"templates" => ["planning/ask_architecture_questions.md"],
|
|
29
38
|
"description" => "Architecture Gate Questions",
|
|
30
39
|
"outs" => ["docs/arch_gate_questions.md"],
|
|
31
40
|
"gate" => true
|
|
32
41
|
},
|
|
33
42
|
"03_ADR_FACTORY" => {
|
|
34
|
-
"templates" => ["
|
|
43
|
+
"templates" => ["planning/generate_adrs.md"],
|
|
35
44
|
"description" => "Generate Architecture Decision Records",
|
|
36
45
|
"outs" => ["docs/adr/*.md"],
|
|
37
46
|
"gate" => false
|
|
38
47
|
},
|
|
39
48
|
"04_DOMAIN_DECOMPOSITION" => {
|
|
40
|
-
"templates" => ["
|
|
49
|
+
"templates" => ["planning/decompose_domain.md"],
|
|
41
50
|
"description" => "Decompose Domain into Components",
|
|
42
51
|
"outs" => ["docs/domain_decomposition.md"],
|
|
43
|
-
"gate" => false
|
|
52
|
+
"gate" => false # Auto-generated
|
|
44
53
|
},
|
|
45
54
|
"05_API_DESIGN" => {
|
|
46
|
-
"templates" => ["
|
|
55
|
+
"templates" => ["planning/design_apis.md"],
|
|
47
56
|
"description" => "Design APIs and Interfaces",
|
|
48
57
|
"outs" => ["docs/api_design.md"],
|
|
49
|
-
"gate" => false
|
|
58
|
+
"gate" => false # Auto-generated
|
|
50
59
|
},
|
|
51
60
|
"06_DATA_MODEL" => {
|
|
52
|
-
"templates" => ["
|
|
61
|
+
"templates" => ["planning/design_data_model.md"],
|
|
53
62
|
"description" => "Design Data Model",
|
|
54
63
|
"outs" => ["docs/data_model.md"],
|
|
55
64
|
"gate" => true
|
|
56
65
|
},
|
|
57
66
|
"07_SECURITY_REVIEW" => {
|
|
58
|
-
"templates" => ["
|
|
67
|
+
"templates" => ["planning/plan_testing.md"],
|
|
59
68
|
"description" => "Security Review and Threat Model",
|
|
60
69
|
"outs" => ["docs/security_review.md"],
|
|
61
70
|
"gate" => true
|
|
62
71
|
},
|
|
63
72
|
"08_PERFORMANCE_REVIEW" => {
|
|
64
|
-
"templates" => ["
|
|
73
|
+
"templates" => ["planning/create_tasks.md"],
|
|
65
74
|
"description" => "Performance Review and Optimization",
|
|
66
75
|
"outs" => ["docs/performance_review.md"],
|
|
67
76
|
"gate" => true
|
|
68
77
|
},
|
|
69
78
|
"09_RELIABILITY_REVIEW" => {
|
|
70
|
-
"templates" => ["
|
|
79
|
+
"templates" => ["implementation/setup_scaffolding.md"],
|
|
71
80
|
"description" => "Reliability Review and SLOs",
|
|
72
81
|
"outs" => ["docs/reliability_review.md"],
|
|
73
82
|
"gate" => true
|
|
74
83
|
},
|
|
75
84
|
"10_TESTING_STRATEGY" => {
|
|
76
|
-
"templates" => ["
|
|
85
|
+
"templates" => ["implementation/implement_features.md"],
|
|
77
86
|
"description" => "Define Testing Strategy",
|
|
78
87
|
"outs" => ["docs/testing_strategy.md"],
|
|
79
|
-
"gate" => false
|
|
88
|
+
"gate" => false # Auto-generated
|
|
80
89
|
},
|
|
81
90
|
"11_STATIC_ANALYSIS" => {
|
|
82
|
-
"templates" => ["
|
|
91
|
+
"templates" => ["implementation/configure_static_analysis.md"],
|
|
83
92
|
"description" => "Static Code Analysis",
|
|
84
93
|
"outs" => ["docs/static_analysis.md"],
|
|
85
94
|
"gate" => false
|
|
86
95
|
},
|
|
87
96
|
"12_OBSERVABILITY_SLOS" => {
|
|
88
|
-
"templates" => ["
|
|
97
|
+
"templates" => ["planning/plan_observability.md"],
|
|
89
98
|
"description" => "Define Observability and SLOs",
|
|
90
99
|
"outs" => ["docs/observability_slos.md"],
|
|
91
100
|
"gate" => true
|
|
92
101
|
},
|
|
93
102
|
"13_DELIVERY_ROLLOUT" => {
|
|
94
|
-
"templates" => ["
|
|
103
|
+
"templates" => ["implementation/plan_delivery.md"],
|
|
95
104
|
"description" => "Plan Delivery and Rollout",
|
|
96
105
|
"outs" => ["docs/delivery_rollout.md"],
|
|
97
106
|
"gate" => true
|
|
98
107
|
},
|
|
99
108
|
"14_DOCS_PORTAL" => {
|
|
100
|
-
"templates" => ["
|
|
109
|
+
"templates" => ["implementation/create_documentation_portal.md"],
|
|
101
110
|
"description" => "Documentation Portal",
|
|
102
111
|
"outs" => ["docs/docs_portal.md"],
|
|
103
112
|
"gate" => false
|
|
104
113
|
},
|
|
105
114
|
"15_POST_RELEASE" => {
|
|
106
|
-
"templates" => ["
|
|
115
|
+
"templates" => ["implementation/review_post_release.md"],
|
|
107
116
|
"description" => "Post-Release Review",
|
|
108
117
|
"outs" => ["docs/post_release.md"],
|
|
109
|
-
"gate" => false
|
|
118
|
+
"gate" => false # Auto-generated
|
|
110
119
|
},
|
|
111
120
|
# New implementation step for actual development work
|
|
112
121
|
"16_IMPLEMENTATION" => {
|
|
113
|
-
"templates" => ["implementation.md"],
|
|
122
|
+
"templates" => ["implementation/implement_features.md"], # Reuse existing implementation template
|
|
114
123
|
"description" => "Execute Implementation Tasks",
|
|
115
124
|
"outs" => ["implementation_log.md"],
|
|
116
125
|
"gate" => false,
|
|
117
|
-
"implementation" => true
|
|
126
|
+
"implementation" => true # Special step that runs development tasks
|
|
118
127
|
}
|
|
119
128
|
}.freeze
|
|
120
129
|
end
|
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "prompt_manager"
|
|
4
|
+
require_relative "checkpoint"
|
|
5
|
+
require_relative "checkpoint_display"
|
|
6
|
+
require_relative "../harness/test_runner"
|
|
7
|
+
|
|
8
|
+
module Aidp
|
|
9
|
+
module Execute
|
|
10
|
+
# Executes work loops for a single step
|
|
11
|
+
# Responsibilities:
|
|
12
|
+
# - Create initial PROMPT.md from templates and context
|
|
13
|
+
# - Loop: send PROMPT.md to agent, run tests/linters, check completion
|
|
14
|
+
# - Only send test/lint failures back to agent
|
|
15
|
+
# - Track iteration count
|
|
16
|
+
# - Record periodic checkpoints with metrics
|
|
17
|
+
class WorkLoopRunner
|
|
18
|
+
include Aidp::MessageDisplay
|
|
19
|
+
|
|
20
|
+
attr_reader :iteration_count, :project_dir
|
|
21
|
+
|
|
22
|
+
MAX_ITERATIONS = 50 # Safety limit
|
|
23
|
+
CHECKPOINT_INTERVAL = 5 # Record checkpoint every N iterations
|
|
24
|
+
|
|
25
|
+
def initialize(project_dir, provider_manager, config, options = {})
|
|
26
|
+
@project_dir = project_dir
|
|
27
|
+
@provider_manager = provider_manager
|
|
28
|
+
@config = config
|
|
29
|
+
@prompt_manager = PromptManager.new(project_dir)
|
|
30
|
+
@test_runner = Aidp::Harness::TestRunner.new(project_dir, config)
|
|
31
|
+
@checkpoint = Checkpoint.new(project_dir)
|
|
32
|
+
@checkpoint_display = CheckpointDisplay.new
|
|
33
|
+
@iteration_count = 0
|
|
34
|
+
@step_name = nil
|
|
35
|
+
@options = options
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Execute a step using work loop pattern
|
|
39
|
+
# Returns final result when step is complete
|
|
40
|
+
def execute_step(step_name, step_spec, context = {})
|
|
41
|
+
@step_name = step_name
|
|
42
|
+
@iteration_count = 0
|
|
43
|
+
|
|
44
|
+
display_message("🔄 Starting work loop for step: #{step_name}", type: :info)
|
|
45
|
+
|
|
46
|
+
# Create initial PROMPT.md
|
|
47
|
+
create_initial_prompt(step_spec, context)
|
|
48
|
+
|
|
49
|
+
# Main work loop
|
|
50
|
+
loop do
|
|
51
|
+
@iteration_count += 1
|
|
52
|
+
display_message(" Iteration #{@iteration_count}", type: :info)
|
|
53
|
+
|
|
54
|
+
break if @iteration_count > MAX_ITERATIONS
|
|
55
|
+
|
|
56
|
+
# Send PROMPT.md to agent
|
|
57
|
+
result = send_to_agent
|
|
58
|
+
|
|
59
|
+
# Run tests and linters
|
|
60
|
+
test_results = @test_runner.run_tests
|
|
61
|
+
lint_results = @test_runner.run_linters
|
|
62
|
+
|
|
63
|
+
# Record checkpoint at intervals
|
|
64
|
+
record_periodic_checkpoint(test_results, lint_results)
|
|
65
|
+
|
|
66
|
+
# Check if step is complete
|
|
67
|
+
if step_complete?(result, test_results, lint_results)
|
|
68
|
+
# Record final checkpoint
|
|
69
|
+
record_final_checkpoint(test_results, lint_results)
|
|
70
|
+
display_message("✅ Step #{step_name} completed after #{@iteration_count} iterations", type: :success)
|
|
71
|
+
archive_and_cleanup
|
|
72
|
+
return build_success_result(result)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# If not complete, prepare next iteration with failures (if any)
|
|
76
|
+
prepare_next_iteration(test_results, lint_results)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Safety: max iterations reached
|
|
80
|
+
display_message("⚠️ Max iterations (#{MAX_ITERATIONS}) reached for #{step_name}", type: :warning)
|
|
81
|
+
archive_and_cleanup
|
|
82
|
+
build_max_iterations_result
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
private
|
|
86
|
+
|
|
87
|
+
# Create initial PROMPT.md with all context
|
|
88
|
+
def create_initial_prompt(step_spec, context)
|
|
89
|
+
template_content = load_template(step_spec["templates"]&.first)
|
|
90
|
+
prd_content = load_prd
|
|
91
|
+
style_guide = load_style_guide
|
|
92
|
+
user_input = format_user_input(context[:user_input])
|
|
93
|
+
|
|
94
|
+
initial_prompt = build_initial_prompt_content(
|
|
95
|
+
template: template_content,
|
|
96
|
+
prd: prd_content,
|
|
97
|
+
style_guide: style_guide,
|
|
98
|
+
user_input: user_input,
|
|
99
|
+
step_name: @step_name
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
@prompt_manager.write(initial_prompt)
|
|
103
|
+
display_message(" Created PROMPT.md (#{initial_prompt.length} chars)", type: :info)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def build_initial_prompt_content(template:, prd:, style_guide:, user_input:, step_name:)
|
|
107
|
+
parts = []
|
|
108
|
+
|
|
109
|
+
parts << "# Work Loop: #{step_name}"
|
|
110
|
+
parts << ""
|
|
111
|
+
parts << "## Instructions"
|
|
112
|
+
parts << "You are working in a work loop. Your responsibilities:"
|
|
113
|
+
parts << "1. Read this PROMPT.md file to understand what needs to be done"
|
|
114
|
+
parts << "2. Complete the work described below"
|
|
115
|
+
parts << "3. **IMPORTANT**: Edit this PROMPT.md file yourself to:"
|
|
116
|
+
parts << " - Remove completed items"
|
|
117
|
+
parts << " - Update with current status"
|
|
118
|
+
parts << " - Keep it concise (remove unnecessary context)"
|
|
119
|
+
parts << " - Mark the step COMPLETE when 100% done"
|
|
120
|
+
parts << "4. After you finish, tests and linters will run automatically"
|
|
121
|
+
parts << "5. If tests/linters fail, you'll see the errors in the next iteration"
|
|
122
|
+
parts << ""
|
|
123
|
+
parts << "## Completion Criteria"
|
|
124
|
+
parts << "Mark this step COMPLETE by adding this line to PROMPT.md:"
|
|
125
|
+
parts << "```"
|
|
126
|
+
parts << "STATUS: COMPLETE"
|
|
127
|
+
parts << "```"
|
|
128
|
+
parts << ""
|
|
129
|
+
|
|
130
|
+
if user_input && !user_input.empty?
|
|
131
|
+
parts << "## User Input"
|
|
132
|
+
parts << user_input
|
|
133
|
+
parts << ""
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
if style_guide
|
|
137
|
+
parts << "## LLM Style Guide"
|
|
138
|
+
parts << style_guide
|
|
139
|
+
parts << ""
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
if prd
|
|
143
|
+
parts << "## Product Requirements (PRD)"
|
|
144
|
+
parts << prd
|
|
145
|
+
parts << ""
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
parts << "## Task Template"
|
|
149
|
+
parts << template
|
|
150
|
+
parts << ""
|
|
151
|
+
|
|
152
|
+
parts.join("\n")
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def send_to_agent
|
|
156
|
+
prompt_content = @prompt_manager.read
|
|
157
|
+
return {status: "error", message: "PROMPT.md not found"} unless prompt_content
|
|
158
|
+
|
|
159
|
+
# Send to provider via provider_manager
|
|
160
|
+
@provider_manager.execute_with_provider(
|
|
161
|
+
@provider_manager.current_provider,
|
|
162
|
+
prompt_content,
|
|
163
|
+
{
|
|
164
|
+
step_name: @step_name,
|
|
165
|
+
iteration: @iteration_count,
|
|
166
|
+
project_dir: @project_dir
|
|
167
|
+
}
|
|
168
|
+
)
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def step_complete?(agent_result, test_results, lint_results)
|
|
172
|
+
# Check if agent marked step complete
|
|
173
|
+
agent_complete = agent_result[:status] == "completed" || prompt_marked_complete?
|
|
174
|
+
|
|
175
|
+
# Check if tests and linters pass
|
|
176
|
+
tests_pass = test_results[:success]
|
|
177
|
+
linters_pass = lint_results[:success]
|
|
178
|
+
|
|
179
|
+
agent_complete && tests_pass && linters_pass
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def prompt_marked_complete?
|
|
183
|
+
prompt_content = @prompt_manager.read
|
|
184
|
+
return false unless prompt_content
|
|
185
|
+
|
|
186
|
+
# Check for STATUS: COMPLETE marker
|
|
187
|
+
prompt_content.match?(/^STATUS:\s*COMPLETE/i)
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
def prepare_next_iteration(test_results, lint_results)
|
|
191
|
+
# Only append failures to PROMPT.md for agent to see
|
|
192
|
+
failures = []
|
|
193
|
+
|
|
194
|
+
unless test_results[:success]
|
|
195
|
+
failures << "## Test Failures"
|
|
196
|
+
failures << test_results[:output]
|
|
197
|
+
failures << ""
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
unless lint_results[:success]
|
|
201
|
+
failures << "## Linter Failures"
|
|
202
|
+
failures << lint_results[:output]
|
|
203
|
+
failures << ""
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
return if failures.empty?
|
|
207
|
+
|
|
208
|
+
# Append failures to PROMPT.md
|
|
209
|
+
current_prompt = @prompt_manager.read
|
|
210
|
+
updated_prompt = current_prompt + "\n\n---\n\n" + failures.join("\n")
|
|
211
|
+
@prompt_manager.write(updated_prompt)
|
|
212
|
+
|
|
213
|
+
display_message(" Added failure reports to PROMPT.md", type: :warning)
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
def archive_and_cleanup
|
|
217
|
+
@prompt_manager.archive(@step_name)
|
|
218
|
+
@prompt_manager.delete
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
def load_template(template_name)
|
|
222
|
+
return "" unless template_name
|
|
223
|
+
|
|
224
|
+
# Template name now includes subdirectory (e.g., "planning/create_prd.md")
|
|
225
|
+
template_path = File.join(@project_dir, "templates", template_name)
|
|
226
|
+
return File.read(template_path) if File.exist?(template_path)
|
|
227
|
+
|
|
228
|
+
# Fallback: try COMMON directory
|
|
229
|
+
common_path = File.join(@project_dir, "templates", "COMMON", template_name)
|
|
230
|
+
return File.read(common_path) if File.exist?(common_path)
|
|
231
|
+
|
|
232
|
+
""
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
def load_prd
|
|
236
|
+
prd_path = File.join(@project_dir, "docs", "prd.md")
|
|
237
|
+
File.exist?(prd_path) ? File.read(prd_path) : nil
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
def load_style_guide
|
|
241
|
+
style_guide_path = File.join(@project_dir, "docs", "LLM_STYLE_GUIDE.md")
|
|
242
|
+
File.exist?(style_guide_path) ? File.read(style_guide_path) : nil
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
def format_user_input(user_input)
|
|
246
|
+
return nil if user_input.nil? || user_input.empty?
|
|
247
|
+
|
|
248
|
+
lines = user_input.map { |key, value| "- **#{key}**: #{value}" }
|
|
249
|
+
lines.join("\n")
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
def build_success_result(agent_result)
|
|
253
|
+
{
|
|
254
|
+
status: "completed",
|
|
255
|
+
message: "Step #{@step_name} completed successfully",
|
|
256
|
+
iterations: @iteration_count,
|
|
257
|
+
final_result: agent_result
|
|
258
|
+
}
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
def build_max_iterations_result
|
|
262
|
+
{
|
|
263
|
+
status: "error",
|
|
264
|
+
message: "Maximum iterations reached",
|
|
265
|
+
iterations: @iteration_count,
|
|
266
|
+
error: "Step did not complete within #{MAX_ITERATIONS} iterations"
|
|
267
|
+
}
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
# Record checkpoint at regular intervals
|
|
271
|
+
def record_periodic_checkpoint(test_results, lint_results)
|
|
272
|
+
# Record every CHECKPOINT_INTERVAL iterations or on iteration 1
|
|
273
|
+
return unless @iteration_count == 1 || (@iteration_count % CHECKPOINT_INTERVAL == 0)
|
|
274
|
+
|
|
275
|
+
metrics = {
|
|
276
|
+
tests_passing: test_results[:success],
|
|
277
|
+
linters_passing: lint_results[:success]
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
checkpoint_data = @checkpoint.record_checkpoint(@step_name, @iteration_count, metrics)
|
|
281
|
+
|
|
282
|
+
# Display inline progress
|
|
283
|
+
@checkpoint_display.display_inline_progress(@iteration_count, checkpoint_data[:metrics])
|
|
284
|
+
|
|
285
|
+
# Show detailed checkpoint every 10 iterations
|
|
286
|
+
if @iteration_count % 10 == 0
|
|
287
|
+
@checkpoint_display.display_checkpoint(checkpoint_data)
|
|
288
|
+
end
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
# Record final checkpoint when step completes
|
|
292
|
+
def record_final_checkpoint(test_results, lint_results)
|
|
293
|
+
metrics = {
|
|
294
|
+
tests_passing: test_results[:success],
|
|
295
|
+
linters_passing: lint_results[:success],
|
|
296
|
+
completed: true
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
checkpoint_data = @checkpoint.record_checkpoint(@step_name, @iteration_count, metrics)
|
|
300
|
+
@checkpoint_display.display_checkpoint(checkpoint_data, show_details: true)
|
|
301
|
+
|
|
302
|
+
# Display progress summary
|
|
303
|
+
summary = @checkpoint.progress_summary
|
|
304
|
+
@checkpoint_display.display_progress_summary(summary) if summary
|
|
305
|
+
end
|
|
306
|
+
end
|
|
307
|
+
end
|
|
308
|
+
end
|