aidp 0.34.0 → 0.39.3
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 +14 -14
- data/lib/aidp/agent_harness_adapter.rb +309 -0
- data/lib/aidp/cli/issue_importer.rb +20 -6
- data/lib/aidp/cli/jobs_command.rb +1 -1
- data/lib/aidp/cli/models_command.rb +9 -16
- data/lib/aidp/cli/prompts_command.rb +253 -0
- data/lib/aidp/cli/security_command.rb +4 -10
- data/lib/aidp/cli/storage_command.rb +277 -0
- data/lib/aidp/cli/temporal_command.rb +460 -0
- data/lib/aidp/cli.rb +24 -2
- data/lib/aidp/config.rb +15 -0
- data/lib/aidp/config_paths.rb +12 -2
- data/lib/aidp/database/migrations.rb +114 -0
- data/lib/aidp/database/repositories/checkpoint_repository.rb +174 -0
- data/lib/aidp/database/repositories/deprecated_models_repository.rb +206 -0
- data/lib/aidp/database/repositories/evaluation_repository.rb +208 -0
- data/lib/aidp/database/repositories/harness_state_repository.rb +140 -0
- data/lib/aidp/database/repositories/job_repository.rb +278 -0
- data/lib/aidp/database/repositories/model_cache_repository.rb +194 -0
- data/lib/aidp/database/repositories/progress_repository.rb +210 -0
- data/lib/aidp/database/repositories/prompt_archive_repository.rb +195 -0
- data/lib/aidp/database/repositories/prompt_feedback_repository.rb +266 -0
- data/lib/aidp/database/repositories/provider_info_cache_repository.rb +208 -0
- data/lib/aidp/database/repositories/provider_metrics_repository.rb +245 -0
- data/lib/aidp/database/repositories/secrets_repository.rb +187 -0
- data/lib/aidp/database/repositories/task_repository.rb +231 -0
- data/lib/aidp/database/repositories/template_version_repository.rb +383 -0
- data/lib/aidp/database/repositories/watch_state_repository.rb +472 -0
- data/lib/aidp/database/repositories/workstream_repository.rb +310 -0
- data/lib/aidp/database/repositories/worktree_repository.rb +240 -0
- data/lib/aidp/database/repository.rb +166 -0
- data/lib/aidp/database/schema.rb +347 -0
- data/lib/aidp/database/storage_migrator.rb +693 -0
- data/lib/aidp/database.rb +139 -0
- data/lib/aidp/debug_mixin.rb +2 -2
- data/lib/aidp/execute/checkpoint.rb +26 -0
- data/lib/aidp/execute/checkpoint_display.rb +16 -0
- data/lib/aidp/execute/prompt_evaluator.rb +30 -57
- data/lib/aidp/execute/work_loop_runner.rb +294 -46
- data/lib/aidp/firewall/provider_requirements_collector.rb +7 -23
- data/lib/aidp/harness/agent_harness_provider_manager.rb +275 -0
- data/lib/aidp/harness/ai_decision_engine.rb +78 -296
- data/lib/aidp/harness/config_schema.rb +59 -0
- data/lib/aidp/harness/configuration.rb +229 -10
- data/lib/aidp/harness/deprecation_cache.rb +53 -11
- data/lib/aidp/harness/enhanced_runner.rb +1 -1
- data/lib/aidp/harness/error_handler.rb +11 -5
- data/lib/aidp/harness/provider_config.rb +36 -0
- data/lib/aidp/harness/provider_factory.rb +26 -24
- data/lib/aidp/harness/provider_info.rb +9 -5
- data/lib/aidp/harness/provider_manager.rb +2 -2
- data/lib/aidp/harness/provider_metrics.rb +103 -1
- data/lib/aidp/harness/rspec_command_optimizer.rb +166 -0
- data/lib/aidp/harness/ruby_llm_registry.rb +97 -8
- data/lib/aidp/harness/runner.rb +7 -2
- data/lib/aidp/harness/state_manager.rb +1 -1
- data/lib/aidp/harness/test_runner.rb +167 -11
- data/lib/aidp/harness/thinking_depth_manager.rb +564 -5
- data/lib/aidp/harness/ui.rb +11 -0
- data/lib/aidp/harness/usage_limit.rb +179 -0
- data/lib/aidp/harness/usage_limit_enforcer.rb +240 -0
- data/lib/aidp/harness/usage_limit_tracker.rb +308 -0
- data/lib/aidp/harness/usage_period.rb +234 -0
- data/lib/aidp/init/agent_instructions_generator.rb +317 -0
- data/lib/aidp/init/runner.rb +25 -4
- data/lib/aidp/interfaces/activity_monitor_interface.rb +234 -0
- data/lib/aidp/interfaces/binary_checker_interface.rb +232 -0
- data/lib/aidp/interfaces/command_executor_interface.rb +216 -0
- data/lib/aidp/interfaces/logger_interface.rb +150 -0
- data/lib/aidp/interfaces/ui_interface.rb +254 -0
- data/lib/aidp/logger.rb +25 -7
- data/lib/aidp/orchestration_adapter.rb +279 -0
- data/lib/aidp/pr_worktree_manager.rb +4 -15
- data/lib/aidp/prompts/feedback_collector.rb +198 -0
- data/lib/aidp/prompts/prompt_template_manager.rb +415 -0
- data/lib/aidp/prompts/template_evolver.rb +305 -0
- data/lib/aidp/prompts/template_version_manager.rb +325 -0
- data/lib/aidp/setup/in_memory_config_adapter.rb +257 -0
- data/lib/aidp/setup/in_memory_config_manager.rb +262 -0
- data/lib/aidp/setup/wizard.rb +616 -176
- data/lib/aidp/shell_executor.rb +58 -0
- data/lib/aidp/style_guide/selector.rb +56 -1
- data/lib/aidp/temporal/activities/analyze_issue_activity.rb +198 -0
- data/lib/aidp/temporal/activities/analyze_sub_task_activity.rb +163 -0
- data/lib/aidp/temporal/activities/base_activity.rb +78 -0
- data/lib/aidp/temporal/activities/create_plan_activity.rb +115 -0
- data/lib/aidp/temporal/activities/create_pr_activity.rb +193 -0
- data/lib/aidp/temporal/activities/create_prompt_activity.rb +118 -0
- data/lib/aidp/temporal/activities/diagnose_failure_activity.rb +228 -0
- data/lib/aidp/temporal/activities/prepare_next_iteration_activity.rb +147 -0
- data/lib/aidp/temporal/activities/record_checkpoint_activity.rb +81 -0
- data/lib/aidp/temporal/activities/run_agent_activity.rb +139 -0
- data/lib/aidp/temporal/activities/run_tests_activity.rb +114 -0
- data/lib/aidp/temporal/activities/run_work_loop_iteration_activity.rb +249 -0
- data/lib/aidp/temporal/configuration.rb +169 -0
- data/lib/aidp/temporal/connection.rb +93 -0
- data/lib/aidp/temporal/worker.rb +119 -0
- data/lib/aidp/temporal/workflow_client.rb +171 -0
- data/lib/aidp/temporal/workflows/base_workflow.rb +73 -0
- data/lib/aidp/temporal/workflows/issue_to_pr_workflow.rb +278 -0
- data/lib/aidp/temporal/workflows/sub_issue_workflow.rb +253 -0
- data/lib/aidp/temporal/workflows/work_loop_workflow.rb +321 -0
- data/lib/aidp/temporal.rb +120 -0
- data/lib/aidp/version.rb +1 -1
- data/lib/aidp/watch/base_processor.rb +35 -0
- data/lib/aidp/watch/build_processor.rb +132 -0
- data/lib/aidp/watch/github_state_extractor.rb +0 -8
- data/lib/aidp/watch/rebase_processor.rb +245 -0
- data/lib/aidp/watch/round_robin_scheduler.rb +150 -0
- data/lib/aidp/watch/runner.rb +366 -285
- data/lib/aidp/watch/state_store.rb +80 -0
- data/lib/aidp/watch/work_item.rb +96 -0
- data/lib/aidp/watch/worktree_cleanup_job.rb +226 -0
- data/lib/aidp/watch/worktree_reconciler.rb +643 -0
- data/lib/aidp/workflows/guided_agent.rb +1 -1
- data/lib/aidp/worktree.rb +62 -0
- data/templates/aidp.yml.example +70 -0
- metadata +118 -16
- data/lib/aidp/analyze/json_file_storage.rb +0 -292
- data/lib/aidp/execute/future_work_backlog.rb +0 -411
- data/lib/aidp/providers/adapter.rb +0 -239
- data/lib/aidp/providers/aider.rb +0 -264
- data/lib/aidp/providers/anthropic.rb +0 -614
- data/lib/aidp/providers/base.rb +0 -617
- data/lib/aidp/providers/capability_registry.rb +0 -204
- data/lib/aidp/providers/codex.rb +0 -267
- data/lib/aidp/providers/cursor.rb +0 -304
- data/lib/aidp/providers/error_taxonomy.rb +0 -195
- data/lib/aidp/providers/gemini.rb +0 -110
- data/lib/aidp/providers/github_copilot.rb +0 -227
- data/lib/aidp/providers/kilocode.rb +0 -157
- data/lib/aidp/providers/opencode.rb +0 -139
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "open3"
|
|
4
|
+
require_relative "base_activity"
|
|
5
|
+
|
|
6
|
+
module Aidp
|
|
7
|
+
module Temporal
|
|
8
|
+
module Activities
|
|
9
|
+
# Activity that creates a pull request from implemented changes
|
|
10
|
+
# Handles git operations and GitHub PR creation
|
|
11
|
+
class CreatePrActivity < BaseActivity
|
|
12
|
+
def execute(input)
|
|
13
|
+
with_activity_context do
|
|
14
|
+
project_dir = input[:project_dir]
|
|
15
|
+
issue_number = input[:issue_number]
|
|
16
|
+
implementation = input[:implementation]
|
|
17
|
+
iterations = input[:iterations]
|
|
18
|
+
|
|
19
|
+
# Validate issue_number is numeric to prevent injection
|
|
20
|
+
unless issue_number.to_s.match?(/\A\d+\z/)
|
|
21
|
+
return error_result("Invalid issue number: must be numeric")
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
log_activity("creating_pr",
|
|
25
|
+
project_dir: project_dir,
|
|
26
|
+
issue_number: issue_number)
|
|
27
|
+
|
|
28
|
+
# Ensure we have commits to push
|
|
29
|
+
unless has_uncommitted_changes?(project_dir) || has_unpushed_commits?(project_dir)
|
|
30
|
+
return error_result("No changes to create PR from")
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Create branch if needed
|
|
34
|
+
branch_name = ensure_branch(project_dir, issue_number)
|
|
35
|
+
|
|
36
|
+
heartbeat(phase: "branch_created", branch: branch_name)
|
|
37
|
+
|
|
38
|
+
# Commit any uncommitted changes
|
|
39
|
+
if has_uncommitted_changes?(project_dir)
|
|
40
|
+
commit_changes(project_dir, issue_number, iterations)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
heartbeat(phase: "changes_committed")
|
|
44
|
+
|
|
45
|
+
# Push branch
|
|
46
|
+
push_branch(project_dir, branch_name)
|
|
47
|
+
|
|
48
|
+
heartbeat(phase: "branch_pushed")
|
|
49
|
+
|
|
50
|
+
# Create PR
|
|
51
|
+
pr_result = create_pull_request(
|
|
52
|
+
project_dir: project_dir,
|
|
53
|
+
branch_name: branch_name,
|
|
54
|
+
issue_number: issue_number,
|
|
55
|
+
implementation: implementation,
|
|
56
|
+
iterations: iterations
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
if pr_result[:success]
|
|
60
|
+
success_result(
|
|
61
|
+
pr_url: pr_result[:pr_url],
|
|
62
|
+
pr_number: pr_result[:pr_number],
|
|
63
|
+
branch: branch_name
|
|
64
|
+
)
|
|
65
|
+
else
|
|
66
|
+
error_result(pr_result[:error] || "Failed to create PR")
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
private
|
|
72
|
+
|
|
73
|
+
def has_uncommitted_changes?(project_dir)
|
|
74
|
+
stdout, _stderr, status = Open3.capture3("git", "status", "--porcelain", chdir: project_dir)
|
|
75
|
+
status.success? && !stdout.strip.empty?
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def has_unpushed_commits?(project_dir)
|
|
79
|
+
stdout, _stderr, status = Open3.capture3(
|
|
80
|
+
"git", "rev-list", "--count", "@{upstream}..HEAD",
|
|
81
|
+
chdir: project_dir
|
|
82
|
+
)
|
|
83
|
+
status.success? && stdout.strip.to_i > 0
|
|
84
|
+
rescue
|
|
85
|
+
false
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def ensure_branch(project_dir, issue_number)
|
|
89
|
+
# issue_number is validated as numeric in execute()
|
|
90
|
+
branch_name = "aidp/issue-#{issue_number}"
|
|
91
|
+
|
|
92
|
+
stdout, _stderr, _status = Open3.capture3("git", "branch", "--show-current", chdir: project_dir)
|
|
93
|
+
current_branch = stdout.strip
|
|
94
|
+
|
|
95
|
+
if current_branch != branch_name
|
|
96
|
+
# Check if branch exists using array-style system call
|
|
97
|
+
ref_path = "refs/heads/#{branch_name}"
|
|
98
|
+
_stdout, _stderr, status = Open3.capture3(
|
|
99
|
+
"git", "show-ref", "--verify", "--quiet", ref_path,
|
|
100
|
+
chdir: project_dir
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
if status.success?
|
|
104
|
+
Open3.capture3("git", "checkout", branch_name, chdir: project_dir)
|
|
105
|
+
else
|
|
106
|
+
Open3.capture3("git", "checkout", "-b", branch_name, chdir: project_dir)
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
branch_name
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def commit_changes(project_dir, issue_number, iterations)
|
|
114
|
+
# Stage all changes
|
|
115
|
+
Open3.capture3("git", "add", "-A", chdir: project_dir)
|
|
116
|
+
|
|
117
|
+
# Build commit message (issue_number validated as numeric)
|
|
118
|
+
commit_message = build_commit_message(issue_number, iterations)
|
|
119
|
+
|
|
120
|
+
# Use array-style to avoid shell injection
|
|
121
|
+
Open3.capture3("git", "commit", "-m", commit_message, chdir: project_dir)
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def build_commit_message(issue_number, iterations)
|
|
125
|
+
# issue_number is validated as numeric, iterations is an integer
|
|
126
|
+
safe_iterations = iterations.to_i
|
|
127
|
+
|
|
128
|
+
"fix: implement changes for issue ##{issue_number}\n\n" \
|
|
129
|
+
"Implemented via AIDP Temporal workflow\n" \
|
|
130
|
+
"Iterations: #{safe_iterations}\n\n" \
|
|
131
|
+
"Closes ##{issue_number}"
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def push_branch(project_dir, branch_name)
|
|
135
|
+
# branch_name is constructed from validated issue_number
|
|
136
|
+
Open3.capture3("git", "push", "-u", "origin", branch_name, chdir: project_dir)
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def create_pull_request(project_dir:, branch_name:, issue_number:, implementation:, iterations:)
|
|
140
|
+
# issue_number is validated as numeric
|
|
141
|
+
title = "Fix ##{issue_number}"
|
|
142
|
+
body = build_pr_body(issue_number, implementation, iterations)
|
|
143
|
+
|
|
144
|
+
# Use array-style Open3 to avoid shell injection
|
|
145
|
+
stdout, stderr, status = Open3.capture3(
|
|
146
|
+
"gh", "pr", "create", "--title", title, "--body", body,
|
|
147
|
+
chdir: project_dir
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
if status.success?
|
|
151
|
+
# Extract PR URL from output
|
|
152
|
+
pr_url = stdout.strip
|
|
153
|
+
pr_number = pr_url.split("/").last.to_i
|
|
154
|
+
|
|
155
|
+
{
|
|
156
|
+
success: true,
|
|
157
|
+
pr_url: pr_url,
|
|
158
|
+
pr_number: pr_number
|
|
159
|
+
}
|
|
160
|
+
else
|
|
161
|
+
{
|
|
162
|
+
success: false,
|
|
163
|
+
error: stderr.empty? ? stdout : stderr
|
|
164
|
+
}
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def build_pr_body(issue_number, implementation, iterations)
|
|
169
|
+
# issue_number validated as numeric, iterations is integer
|
|
170
|
+
safe_iterations = iterations.to_i
|
|
171
|
+
|
|
172
|
+
<<~BODY
|
|
173
|
+
## Summary
|
|
174
|
+
|
|
175
|
+
Implements changes requested in ##{issue_number}
|
|
176
|
+
|
|
177
|
+
## Implementation Details
|
|
178
|
+
|
|
179
|
+
This PR was created via AIDP Temporal workflow after #{safe_iterations} iterations of the fix-forward work loop.
|
|
180
|
+
|
|
181
|
+
## Testing
|
|
182
|
+
|
|
183
|
+
- [ ] All tests pass
|
|
184
|
+
- [ ] Lint checks pass
|
|
185
|
+
- [ ] Manual testing completed
|
|
186
|
+
|
|
187
|
+
Closes ##{issue_number}
|
|
188
|
+
BODY
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
end
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base_activity"
|
|
4
|
+
|
|
5
|
+
module Aidp
|
|
6
|
+
module Temporal
|
|
7
|
+
module Activities
|
|
8
|
+
# Activity that creates the initial PROMPT.md for a work loop
|
|
9
|
+
# Assembles context, instructions, and constraints
|
|
10
|
+
class CreatePromptActivity < BaseActivity
|
|
11
|
+
def execute(input)
|
|
12
|
+
with_activity_context do
|
|
13
|
+
project_dir = input[:project_dir]
|
|
14
|
+
step_name = input[:step_name]
|
|
15
|
+
step_spec = input[:step_spec] || {}
|
|
16
|
+
context = input[:context] || {}
|
|
17
|
+
|
|
18
|
+
log_activity("creating_prompt",
|
|
19
|
+
project_dir: project_dir,
|
|
20
|
+
step_name: step_name)
|
|
21
|
+
|
|
22
|
+
# Load configuration
|
|
23
|
+
config = load_config(project_dir)
|
|
24
|
+
|
|
25
|
+
# Create prompt manager
|
|
26
|
+
prompt_manager = Aidp::Execute::PromptManager.new(project_dir, config: config)
|
|
27
|
+
|
|
28
|
+
# Build initial prompt content
|
|
29
|
+
prompt_content = build_prompt_content(
|
|
30
|
+
project_dir: project_dir,
|
|
31
|
+
step_name: step_name,
|
|
32
|
+
step_spec: step_spec,
|
|
33
|
+
context: context
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
# Write the prompt
|
|
37
|
+
prompt_manager.write(prompt_content)
|
|
38
|
+
|
|
39
|
+
heartbeat(phase: "prompt_created", step_name: step_name)
|
|
40
|
+
|
|
41
|
+
success_result(
|
|
42
|
+
prompt_path: prompt_manager.prompt_path,
|
|
43
|
+
prompt_length: prompt_content.length,
|
|
44
|
+
step_name: step_name
|
|
45
|
+
)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
private
|
|
50
|
+
|
|
51
|
+
def build_prompt_content(project_dir:, step_name:, step_spec:, context:)
|
|
52
|
+
sections = []
|
|
53
|
+
|
|
54
|
+
# Header
|
|
55
|
+
sections << "# Implementation Task: #{step_name}"
|
|
56
|
+
sections << ""
|
|
57
|
+
sections << "Generated at: #{Time.now.iso8601}"
|
|
58
|
+
sections << ""
|
|
59
|
+
|
|
60
|
+
# Step specification
|
|
61
|
+
if step_spec[:description]
|
|
62
|
+
sections << "## Objective"
|
|
63
|
+
sections << ""
|
|
64
|
+
sections << step_spec[:description]
|
|
65
|
+
sections << ""
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Context from previous steps
|
|
69
|
+
if context[:previous_output]
|
|
70
|
+
sections << "## Context from Previous Steps"
|
|
71
|
+
sections << ""
|
|
72
|
+
sections << context[:previous_output]
|
|
73
|
+
sections << ""
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Requirements
|
|
77
|
+
if context[:requirements]&.any?
|
|
78
|
+
sections << "## Requirements"
|
|
79
|
+
sections << ""
|
|
80
|
+
context[:requirements].each do |req|
|
|
81
|
+
sections << "- #{req}"
|
|
82
|
+
end
|
|
83
|
+
sections << ""
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Constraints
|
|
87
|
+
sections << "## Constraints"
|
|
88
|
+
sections << ""
|
|
89
|
+
sections << "- Follow the existing code style and patterns"
|
|
90
|
+
sections << "- Make minimal changes to achieve the objective"
|
|
91
|
+
sections << "- Ensure all tests pass after changes"
|
|
92
|
+
sections << "- Do not introduce new dependencies without justification"
|
|
93
|
+
sections << ""
|
|
94
|
+
|
|
95
|
+
# Style guide reference
|
|
96
|
+
style_guide_path = File.join(project_dir, "docs", "LLM_STYLE_GUIDE.md")
|
|
97
|
+
if File.exist?(style_guide_path)
|
|
98
|
+
sections << "## Style Guide"
|
|
99
|
+
sections << ""
|
|
100
|
+
sections << "Please follow the guidelines in `docs/LLM_STYLE_GUIDE.md`"
|
|
101
|
+
sections << ""
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Instructions
|
|
105
|
+
sections << "## Instructions"
|
|
106
|
+
sections << ""
|
|
107
|
+
sections << "1. Review the existing codebase to understand context"
|
|
108
|
+
sections << "2. Implement the changes described in the objective"
|
|
109
|
+
sections << "3. Ensure all tests pass"
|
|
110
|
+
sections << "4. Signal completion when done"
|
|
111
|
+
sections << ""
|
|
112
|
+
|
|
113
|
+
sections.join("\n")
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base_activity"
|
|
4
|
+
|
|
5
|
+
module Aidp
|
|
6
|
+
module Temporal
|
|
7
|
+
module Activities
|
|
8
|
+
# Activity that analyzes test/lint failures and prepares feedback
|
|
9
|
+
# Extracts actionable information from failure output
|
|
10
|
+
class DiagnoseFailureActivity < BaseActivity
|
|
11
|
+
def execute(input)
|
|
12
|
+
with_activity_context do
|
|
13
|
+
project_dir = input[:project_dir]
|
|
14
|
+
iteration = input[:iteration]
|
|
15
|
+
test_result = input[:test_result]
|
|
16
|
+
|
|
17
|
+
log_activity("diagnosing_failure",
|
|
18
|
+
project_dir: project_dir,
|
|
19
|
+
iteration: iteration)
|
|
20
|
+
|
|
21
|
+
# Analyze failure results
|
|
22
|
+
diagnosis = analyze_failures(test_result)
|
|
23
|
+
|
|
24
|
+
# Update PROMPT.md with failure information
|
|
25
|
+
update_prompt_with_diagnosis(project_dir, diagnosis)
|
|
26
|
+
|
|
27
|
+
success_result(
|
|
28
|
+
diagnosis: diagnosis,
|
|
29
|
+
iteration: iteration,
|
|
30
|
+
failure_count: diagnosis[:failure_count]
|
|
31
|
+
)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
def analyze_failures(test_result)
|
|
38
|
+
results = test_result[:results] || {}
|
|
39
|
+
failures = []
|
|
40
|
+
failure_count = 0
|
|
41
|
+
|
|
42
|
+
results.each do |phase, result|
|
|
43
|
+
next if result[:success]
|
|
44
|
+
|
|
45
|
+
phase_failures = extract_failures(phase, result)
|
|
46
|
+
failures.concat(phase_failures)
|
|
47
|
+
failure_count += phase_failures.length
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
{
|
|
51
|
+
failure_count: failure_count,
|
|
52
|
+
failures: failures,
|
|
53
|
+
summary: build_failure_summary(failures),
|
|
54
|
+
recommendations: generate_recommendations(failures)
|
|
55
|
+
}
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def extract_failures(phase, result)
|
|
59
|
+
output = result[:output] || ""
|
|
60
|
+
failures = []
|
|
61
|
+
|
|
62
|
+
case phase.to_sym
|
|
63
|
+
when :test
|
|
64
|
+
# Extract test failure details - process line by line to avoid ReDoS
|
|
65
|
+
extract_test_failures(output, failures, phase)
|
|
66
|
+
when :lint
|
|
67
|
+
# Extract lint errors - line by line processing
|
|
68
|
+
extract_lint_failures(output, failures, phase)
|
|
69
|
+
when :typecheck
|
|
70
|
+
# Extract type errors - line by line processing
|
|
71
|
+
extract_typecheck_failures(output, failures, phase)
|
|
72
|
+
else
|
|
73
|
+
# Generic failure extraction
|
|
74
|
+
failures << {
|
|
75
|
+
type: :unknown,
|
|
76
|
+
phase: phase,
|
|
77
|
+
message: output.slice(0, 500)
|
|
78
|
+
}
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
failures.first(20) # Limit to prevent massive payloads
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def extract_test_failures(output, failures, phase)
|
|
85
|
+
# Process output in chunks separated by blank lines
|
|
86
|
+
current_failure = nil
|
|
87
|
+
|
|
88
|
+
output.each_line do |line|
|
|
89
|
+
if line.match?(/\A\s*(FAIL|ERROR|Failure)/i)
|
|
90
|
+
# Save previous failure if exists
|
|
91
|
+
if current_failure
|
|
92
|
+
failures << {
|
|
93
|
+
type: :test,
|
|
94
|
+
phase: phase,
|
|
95
|
+
message: current_failure.strip.slice(0, 500)
|
|
96
|
+
}
|
|
97
|
+
end
|
|
98
|
+
current_failure = line
|
|
99
|
+
elsif current_failure
|
|
100
|
+
if line.strip.empty?
|
|
101
|
+
# End of failure block
|
|
102
|
+
failures << {
|
|
103
|
+
type: :test,
|
|
104
|
+
phase: phase,
|
|
105
|
+
message: current_failure.strip.slice(0, 500)
|
|
106
|
+
}
|
|
107
|
+
current_failure = nil
|
|
108
|
+
else
|
|
109
|
+
# Continue accumulating failure message
|
|
110
|
+
current_failure += line
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Don't forget the last failure
|
|
116
|
+
if current_failure
|
|
117
|
+
failures << {
|
|
118
|
+
type: :test,
|
|
119
|
+
phase: phase,
|
|
120
|
+
message: current_failure.strip.slice(0, 500)
|
|
121
|
+
}
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def extract_lint_failures(output, failures, phase)
|
|
126
|
+
# Match lint output format: file:line:col: message
|
|
127
|
+
output.each_line do |line|
|
|
128
|
+
if line.match?(/\A[^:]+:\d+:\d+:/)
|
|
129
|
+
failures << {
|
|
130
|
+
type: :lint,
|
|
131
|
+
phase: phase,
|
|
132
|
+
message: line.strip
|
|
133
|
+
}
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def extract_typecheck_failures(output, failures, phase)
|
|
139
|
+
# Match lines containing "error" (case insensitive)
|
|
140
|
+
output.each_line do |line|
|
|
141
|
+
if line.downcase.include?("error")
|
|
142
|
+
failures << {
|
|
143
|
+
type: :typecheck,
|
|
144
|
+
phase: phase,
|
|
145
|
+
message: line.strip
|
|
146
|
+
}
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def build_failure_summary(failures)
|
|
152
|
+
return "No failures found" if failures.empty?
|
|
153
|
+
|
|
154
|
+
by_type = failures.group_by { |f| f[:type] }
|
|
155
|
+
parts = by_type.map { |type, items| "#{items.length} #{type} failure(s)" }
|
|
156
|
+
parts.join(", ")
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def generate_recommendations(failures)
|
|
160
|
+
recommendations = []
|
|
161
|
+
|
|
162
|
+
if failures.any? { |f| f[:type] == :test }
|
|
163
|
+
recommendations << "Review failing test assertions and update implementation"
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
if failures.any? { |f| f[:type] == :lint }
|
|
167
|
+
recommendations << "Fix code style issues to match project standards"
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
if failures.any? { |f| f[:type] == :typecheck }
|
|
171
|
+
recommendations << "Fix type errors to ensure type safety"
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
recommendations
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def update_prompt_with_diagnosis(project_dir, diagnosis)
|
|
178
|
+
prompt_manager = Aidp::Execute::PromptManager.new(project_dir)
|
|
179
|
+
current_prompt = prompt_manager.read
|
|
180
|
+
|
|
181
|
+
return unless current_prompt
|
|
182
|
+
|
|
183
|
+
# Build failure section
|
|
184
|
+
failure_section = build_failure_section(diagnosis)
|
|
185
|
+
|
|
186
|
+
# Append to prompt
|
|
187
|
+
updated_prompt = <<~PROMPT
|
|
188
|
+
#{current_prompt}
|
|
189
|
+
|
|
190
|
+
---
|
|
191
|
+
## Test/Lint Failures (Fix These)
|
|
192
|
+
|
|
193
|
+
#{failure_section}
|
|
194
|
+
|
|
195
|
+
Please address the above failures in this iteration.
|
|
196
|
+
PROMPT
|
|
197
|
+
|
|
198
|
+
prompt_manager.write(updated_prompt)
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
def build_failure_section(diagnosis)
|
|
202
|
+
sections = []
|
|
203
|
+
|
|
204
|
+
sections << "**Summary:** #{diagnosis[:summary]}"
|
|
205
|
+
sections << ""
|
|
206
|
+
|
|
207
|
+
diagnosis[:failures].first(10).each_with_index do |failure, idx|
|
|
208
|
+
sections << "### Failure #{idx + 1} (#{failure[:type]})"
|
|
209
|
+
sections << ""
|
|
210
|
+
sections << "```"
|
|
211
|
+
sections << failure[:message]
|
|
212
|
+
sections << "```"
|
|
213
|
+
sections << ""
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
if diagnosis[:recommendations].any?
|
|
217
|
+
sections << "### Recommendations"
|
|
218
|
+
diagnosis[:recommendations].each do |rec|
|
|
219
|
+
sections << "- #{rec}"
|
|
220
|
+
end
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
sections.join("\n")
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
end
|
|
228
|
+
end
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base_activity"
|
|
4
|
+
|
|
5
|
+
module Aidp
|
|
6
|
+
module Temporal
|
|
7
|
+
module Activities
|
|
8
|
+
# Activity that prepares for the next work loop iteration
|
|
9
|
+
# Updates PROMPT.md with relevant context for continued work
|
|
10
|
+
class PrepareNextIterationActivity < BaseActivity
|
|
11
|
+
def execute(input)
|
|
12
|
+
with_activity_context do
|
|
13
|
+
project_dir = input[:project_dir]
|
|
14
|
+
iteration = input[:iteration]
|
|
15
|
+
test_result = input[:test_result]
|
|
16
|
+
failures_only = input[:failures_only] || true
|
|
17
|
+
|
|
18
|
+
log_activity("preparing_next_iteration",
|
|
19
|
+
project_dir: project_dir,
|
|
20
|
+
iteration: iteration)
|
|
21
|
+
|
|
22
|
+
# Prepare context for next iteration
|
|
23
|
+
preparation = prepare_iteration_context(
|
|
24
|
+
project_dir: project_dir,
|
|
25
|
+
iteration: iteration,
|
|
26
|
+
test_result: test_result,
|
|
27
|
+
failures_only: failures_only
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
success_result(
|
|
31
|
+
iteration: iteration + 1,
|
|
32
|
+
preparation: preparation
|
|
33
|
+
)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
private
|
|
38
|
+
|
|
39
|
+
def prepare_iteration_context(project_dir:, iteration:, test_result:, failures_only:)
|
|
40
|
+
prompt_manager = Aidp::Execute::PromptManager.new(project_dir)
|
|
41
|
+
|
|
42
|
+
# Get current prompt
|
|
43
|
+
current_prompt = prompt_manager.read || ""
|
|
44
|
+
|
|
45
|
+
# Clean up previous iteration markers
|
|
46
|
+
current_prompt = clean_iteration_markers(current_prompt)
|
|
47
|
+
|
|
48
|
+
# Add iteration context
|
|
49
|
+
iteration_header = build_iteration_header(iteration + 1, test_result)
|
|
50
|
+
|
|
51
|
+
# Build focused context based on failures_only flag
|
|
52
|
+
context_section = if failures_only
|
|
53
|
+
build_failures_context(test_result)
|
|
54
|
+
else
|
|
55
|
+
build_full_context(test_result)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Update prompt
|
|
59
|
+
updated_prompt = <<~PROMPT
|
|
60
|
+
#{current_prompt}
|
|
61
|
+
|
|
62
|
+
#{iteration_header}
|
|
63
|
+
|
|
64
|
+
#{context_section}
|
|
65
|
+
PROMPT
|
|
66
|
+
|
|
67
|
+
prompt_manager.write(updated_prompt.strip)
|
|
68
|
+
|
|
69
|
+
{
|
|
70
|
+
prompt_updated: true,
|
|
71
|
+
next_iteration: iteration + 1,
|
|
72
|
+
context_type: failures_only ? "failures_only" : "full"
|
|
73
|
+
}
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def clean_iteration_markers(prompt)
|
|
77
|
+
# Remove previous iteration headers and context
|
|
78
|
+
prompt
|
|
79
|
+
.gsub(/---\n## Iteration \d+ Context.*?(?=---\n##|\z)/m, "")
|
|
80
|
+
.gsub(/---\n## Test\/Lint Failures.*?(?=---\n##|\z)/m, "")
|
|
81
|
+
.strip
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def build_iteration_header(next_iteration, test_result)
|
|
85
|
+
results = test_result[:results] || {}
|
|
86
|
+
passing = results.count { |_, r| r[:success] }
|
|
87
|
+
total = results.count
|
|
88
|
+
|
|
89
|
+
<<~HEADER
|
|
90
|
+
---
|
|
91
|
+
## Iteration #{next_iteration} Context
|
|
92
|
+
|
|
93
|
+
Previous iteration status: #{passing}/#{total} checks passing
|
|
94
|
+
Continuing with fix-forward pattern.
|
|
95
|
+
HEADER
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def build_failures_context(test_result)
|
|
99
|
+
results = test_result[:results] || {}
|
|
100
|
+
failed = results.reject { |_, r| r[:success] }
|
|
101
|
+
|
|
102
|
+
return "All checks passing in previous iteration." if failed.empty?
|
|
103
|
+
|
|
104
|
+
sections = ["### Remaining Failures to Fix", ""]
|
|
105
|
+
|
|
106
|
+
failed.each do |phase, result|
|
|
107
|
+
sections << "#### #{phase.to_s.capitalize}"
|
|
108
|
+
sections << ""
|
|
109
|
+
sections << "```"
|
|
110
|
+
sections << truncate_output(result[:output])
|
|
111
|
+
sections << "```"
|
|
112
|
+
sections << ""
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
sections.join("\n")
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def build_full_context(test_result)
|
|
119
|
+
results = test_result[:results] || {}
|
|
120
|
+
sections = ["### Full Test Results", ""]
|
|
121
|
+
|
|
122
|
+
results.each do |phase, result|
|
|
123
|
+
status = result[:success] ? "PASS" : "FAIL"
|
|
124
|
+
sections << "#### #{phase.to_s.capitalize}: #{status}"
|
|
125
|
+
sections << ""
|
|
126
|
+
sections << "```"
|
|
127
|
+
sections << truncate_output(result[:output])
|
|
128
|
+
sections << "```"
|
|
129
|
+
sections << ""
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
sections.join("\n")
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def truncate_output(output, max_length: 2000)
|
|
136
|
+
return "" unless output
|
|
137
|
+
|
|
138
|
+
if output.length > max_length
|
|
139
|
+
"#{output.slice(0, max_length)}\n... (truncated)"
|
|
140
|
+
else
|
|
141
|
+
output
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
end
|