aidp 0.34.1 → 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.
Files changed (132) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +14 -14
  3. data/lib/aidp/agent_harness_adapter.rb +309 -0
  4. data/lib/aidp/cli/issue_importer.rb +20 -6
  5. data/lib/aidp/cli/jobs_command.rb +1 -1
  6. data/lib/aidp/cli/models_command.rb +9 -16
  7. data/lib/aidp/cli/prompts_command.rb +253 -0
  8. data/lib/aidp/cli/security_command.rb +4 -10
  9. data/lib/aidp/cli/storage_command.rb +277 -0
  10. data/lib/aidp/cli/temporal_command.rb +460 -0
  11. data/lib/aidp/cli.rb +24 -2
  12. data/lib/aidp/config.rb +15 -0
  13. data/lib/aidp/config_paths.rb +12 -2
  14. data/lib/aidp/database/migrations.rb +114 -0
  15. data/lib/aidp/database/repositories/checkpoint_repository.rb +174 -0
  16. data/lib/aidp/database/repositories/deprecated_models_repository.rb +206 -0
  17. data/lib/aidp/database/repositories/evaluation_repository.rb +208 -0
  18. data/lib/aidp/database/repositories/harness_state_repository.rb +140 -0
  19. data/lib/aidp/database/repositories/job_repository.rb +278 -0
  20. data/lib/aidp/database/repositories/model_cache_repository.rb +194 -0
  21. data/lib/aidp/database/repositories/progress_repository.rb +210 -0
  22. data/lib/aidp/database/repositories/prompt_archive_repository.rb +195 -0
  23. data/lib/aidp/database/repositories/prompt_feedback_repository.rb +266 -0
  24. data/lib/aidp/database/repositories/provider_info_cache_repository.rb +208 -0
  25. data/lib/aidp/database/repositories/provider_metrics_repository.rb +245 -0
  26. data/lib/aidp/database/repositories/secrets_repository.rb +187 -0
  27. data/lib/aidp/database/repositories/task_repository.rb +231 -0
  28. data/lib/aidp/database/repositories/template_version_repository.rb +383 -0
  29. data/lib/aidp/database/repositories/watch_state_repository.rb +472 -0
  30. data/lib/aidp/database/repositories/workstream_repository.rb +310 -0
  31. data/lib/aidp/database/repositories/worktree_repository.rb +240 -0
  32. data/lib/aidp/database/repository.rb +166 -0
  33. data/lib/aidp/database/schema.rb +347 -0
  34. data/lib/aidp/database/storage_migrator.rb +693 -0
  35. data/lib/aidp/database.rb +139 -0
  36. data/lib/aidp/debug_mixin.rb +2 -2
  37. data/lib/aidp/execute/checkpoint.rb +26 -0
  38. data/lib/aidp/execute/checkpoint_display.rb +16 -0
  39. data/lib/aidp/execute/prompt_evaluator.rb +30 -57
  40. data/lib/aidp/execute/work_loop_runner.rb +294 -46
  41. data/lib/aidp/firewall/provider_requirements_collector.rb +7 -23
  42. data/lib/aidp/harness/agent_harness_provider_manager.rb +275 -0
  43. data/lib/aidp/harness/ai_decision_engine.rb +78 -296
  44. data/lib/aidp/harness/config_schema.rb +59 -0
  45. data/lib/aidp/harness/configuration.rb +229 -10
  46. data/lib/aidp/harness/deprecation_cache.rb +53 -11
  47. data/lib/aidp/harness/enhanced_runner.rb +1 -1
  48. data/lib/aidp/harness/error_handler.rb +11 -5
  49. data/lib/aidp/harness/provider_config.rb +36 -0
  50. data/lib/aidp/harness/provider_factory.rb +26 -24
  51. data/lib/aidp/harness/provider_info.rb +9 -5
  52. data/lib/aidp/harness/provider_manager.rb +2 -2
  53. data/lib/aidp/harness/provider_metrics.rb +103 -1
  54. data/lib/aidp/harness/rspec_command_optimizer.rb +166 -0
  55. data/lib/aidp/harness/ruby_llm_registry.rb +97 -8
  56. data/lib/aidp/harness/runner.rb +7 -2
  57. data/lib/aidp/harness/state_manager.rb +1 -1
  58. data/lib/aidp/harness/test_runner.rb +167 -11
  59. data/lib/aidp/harness/thinking_depth_manager.rb +564 -5
  60. data/lib/aidp/harness/usage_limit.rb +179 -0
  61. data/lib/aidp/harness/usage_limit_enforcer.rb +240 -0
  62. data/lib/aidp/harness/usage_limit_tracker.rb +308 -0
  63. data/lib/aidp/harness/usage_period.rb +234 -0
  64. data/lib/aidp/init/agent_instructions_generator.rb +317 -0
  65. data/lib/aidp/init/runner.rb +25 -4
  66. data/lib/aidp/interfaces/activity_monitor_interface.rb +234 -0
  67. data/lib/aidp/interfaces/binary_checker_interface.rb +232 -0
  68. data/lib/aidp/interfaces/command_executor_interface.rb +216 -0
  69. data/lib/aidp/interfaces/logger_interface.rb +150 -0
  70. data/lib/aidp/interfaces/ui_interface.rb +254 -0
  71. data/lib/aidp/logger.rb +25 -7
  72. data/lib/aidp/orchestration_adapter.rb +279 -0
  73. data/lib/aidp/pr_worktree_manager.rb +4 -15
  74. data/lib/aidp/prompts/feedback_collector.rb +198 -0
  75. data/lib/aidp/prompts/prompt_template_manager.rb +415 -0
  76. data/lib/aidp/prompts/template_evolver.rb +305 -0
  77. data/lib/aidp/prompts/template_version_manager.rb +325 -0
  78. data/lib/aidp/setup/in_memory_config_adapter.rb +257 -0
  79. data/lib/aidp/setup/in_memory_config_manager.rb +262 -0
  80. data/lib/aidp/setup/wizard.rb +616 -176
  81. data/lib/aidp/shell_executor.rb +58 -0
  82. data/lib/aidp/style_guide/selector.rb +56 -1
  83. data/lib/aidp/temporal/activities/analyze_issue_activity.rb +198 -0
  84. data/lib/aidp/temporal/activities/analyze_sub_task_activity.rb +163 -0
  85. data/lib/aidp/temporal/activities/base_activity.rb +78 -0
  86. data/lib/aidp/temporal/activities/create_plan_activity.rb +115 -0
  87. data/lib/aidp/temporal/activities/create_pr_activity.rb +193 -0
  88. data/lib/aidp/temporal/activities/create_prompt_activity.rb +118 -0
  89. data/lib/aidp/temporal/activities/diagnose_failure_activity.rb +228 -0
  90. data/lib/aidp/temporal/activities/prepare_next_iteration_activity.rb +147 -0
  91. data/lib/aidp/temporal/activities/record_checkpoint_activity.rb +81 -0
  92. data/lib/aidp/temporal/activities/run_agent_activity.rb +139 -0
  93. data/lib/aidp/temporal/activities/run_tests_activity.rb +114 -0
  94. data/lib/aidp/temporal/activities/run_work_loop_iteration_activity.rb +249 -0
  95. data/lib/aidp/temporal/configuration.rb +169 -0
  96. data/lib/aidp/temporal/connection.rb +93 -0
  97. data/lib/aidp/temporal/worker.rb +119 -0
  98. data/lib/aidp/temporal/workflow_client.rb +171 -0
  99. data/lib/aidp/temporal/workflows/base_workflow.rb +73 -0
  100. data/lib/aidp/temporal/workflows/issue_to_pr_workflow.rb +278 -0
  101. data/lib/aidp/temporal/workflows/sub_issue_workflow.rb +253 -0
  102. data/lib/aidp/temporal/workflows/work_loop_workflow.rb +321 -0
  103. data/lib/aidp/temporal.rb +120 -0
  104. data/lib/aidp/version.rb +1 -1
  105. data/lib/aidp/watch/base_processor.rb +35 -0
  106. data/lib/aidp/watch/build_processor.rb +132 -0
  107. data/lib/aidp/watch/github_state_extractor.rb +0 -8
  108. data/lib/aidp/watch/rebase_processor.rb +245 -0
  109. data/lib/aidp/watch/round_robin_scheduler.rb +150 -0
  110. data/lib/aidp/watch/runner.rb +366 -285
  111. data/lib/aidp/watch/state_store.rb +80 -0
  112. data/lib/aidp/watch/work_item.rb +96 -0
  113. data/lib/aidp/watch/worktree_cleanup_job.rb +226 -0
  114. data/lib/aidp/watch/worktree_reconciler.rb +643 -0
  115. data/lib/aidp/workflows/guided_agent.rb +1 -1
  116. data/lib/aidp/worktree.rb +62 -0
  117. data/templates/aidp.yml.example +70 -0
  118. metadata +117 -16
  119. data/lib/aidp/analyze/json_file_storage.rb +0 -292
  120. data/lib/aidp/execute/future_work_backlog.rb +0 -411
  121. data/lib/aidp/providers/adapter.rb +0 -239
  122. data/lib/aidp/providers/aider.rb +0 -264
  123. data/lib/aidp/providers/anthropic.rb +0 -614
  124. data/lib/aidp/providers/base.rb +0 -617
  125. data/lib/aidp/providers/capability_registry.rb +0 -204
  126. data/lib/aidp/providers/codex.rb +0 -267
  127. data/lib/aidp/providers/cursor.rb +0 -304
  128. data/lib/aidp/providers/error_taxonomy.rb +0 -195
  129. data/lib/aidp/providers/gemini.rb +0 -110
  130. data/lib/aidp/providers/github_copilot.rb +0 -227
  131. data/lib/aidp/providers/kilocode.rb +0 -157
  132. data/lib/aidp/providers/opencode.rb +0 -139
@@ -0,0 +1,321 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "temporalio/workflow"
4
+ require_relative "base_workflow"
5
+
6
+ module Aidp
7
+ module Temporal
8
+ module Workflows
9
+ # Workflow that implements the fix-forward work loop pattern
10
+ # Handles iterative implementation with test validation
11
+ #
12
+ # State machine:
13
+ # READY → APPLY_PATCH → TEST → {PASS → DONE | FAIL → DIAGNOSE → NEXT_PATCH} → READY
14
+ class WorkLoopWorkflow < BaseWorkflow
15
+ # Query handlers
16
+ workflow_query
17
+ def current_state
18
+ @state
19
+ end
20
+
21
+ workflow_query
22
+ def iteration_count
23
+ @iteration
24
+ end
25
+
26
+ workflow_query
27
+ def test_results
28
+ @last_test_results
29
+ end
30
+
31
+ workflow_query
32
+ def progress
33
+ {
34
+ state: @state,
35
+ iteration: @iteration,
36
+ max_iterations: @max_iterations,
37
+ step_name: @step_name,
38
+ consecutive_failures: @consecutive_failures,
39
+ started_at: @started_at
40
+ }
41
+ end
42
+
43
+ # Signal handlers
44
+ workflow_signal
45
+ def pause
46
+ @paused = true
47
+ log_workflow("paused", iteration: @iteration)
48
+ end
49
+
50
+ workflow_signal
51
+ def resume
52
+ @paused = false
53
+ log_workflow("resumed", iteration: @iteration)
54
+ end
55
+
56
+ workflow_signal
57
+ def inject_instruction(instruction)
58
+ @instruction_queue ||= []
59
+ @instruction_queue << {
60
+ content: instruction,
61
+ type: :user_input,
62
+ queued_at: Time.now.iso8601
63
+ }
64
+ log_workflow("instruction_queued", queue_size: @instruction_queue.length)
65
+ end
66
+
67
+ workflow_signal
68
+ def escalate_model
69
+ @escalate_requested = true
70
+ log_workflow("escalation_requested", current_iteration: @iteration)
71
+ end
72
+
73
+ # Main workflow execution
74
+ def execute(input)
75
+ initialize_state(input)
76
+ log_workflow("started",
77
+ step_name: @step_name,
78
+ max_iterations: @max_iterations,
79
+ project_dir: @project_dir)
80
+
81
+ begin
82
+ result = run_work_loop
83
+ build_result(result)
84
+ rescue Temporalio::Error::CanceledError
85
+ log_workflow("canceled", state: @state, iteration: @iteration)
86
+ build_canceled_result
87
+ end
88
+ end
89
+
90
+ private
91
+
92
+ def initialize_state(input)
93
+ @project_dir = input[:project_dir]
94
+ @step_name = input[:step_name]
95
+ @step_spec = input[:step_spec]
96
+ @context = input[:context] || {}
97
+ @max_iterations = input[:max_iterations] || 50
98
+ @checkpoint_interval = input[:checkpoint_interval] || 5
99
+
100
+ @state = :ready
101
+ @iteration = 0
102
+ @paused = false
103
+ @escalate_requested = false
104
+ @consecutive_failures = 0
105
+ @last_test_results = nil
106
+ @instruction_queue = []
107
+ @started_at = Time.now.iso8601
108
+ end
109
+
110
+ def run_work_loop
111
+ # Create initial prompt
112
+ transition_to(:initializing)
113
+ prompt_result = create_initial_prompt
114
+
115
+ unless prompt_result[:success]
116
+ return {success: false, reason: "prompt_creation_failed", error: prompt_result[:error]}
117
+ end
118
+
119
+ loop do
120
+ # Handle pause signal
121
+ while @paused
122
+ workflow_sleep(1)
123
+ end
124
+
125
+ # Check cancellation
126
+ return {success: false, reason: "canceled"} if cancellation_requested?
127
+
128
+ @iteration += 1
129
+ log_workflow("iteration_start", iteration: @iteration)
130
+
131
+ # Check iteration limit
132
+ if @iteration > @max_iterations
133
+ log_workflow("max_iterations_reached", max: @max_iterations)
134
+ return {success: false, reason: "max_iterations", iterations: @iteration}
135
+ end
136
+
137
+ # APPLY_PATCH: Send prompt to agent
138
+ transition_to(:apply_patch)
139
+ agent_result = run_agent_activity
140
+
141
+ unless agent_result[:success]
142
+ @consecutive_failures += 1
143
+ if @consecutive_failures >= 3
144
+ handle_escalation
145
+ end
146
+ # Fix-forward: continue to next iteration
147
+ next
148
+ end
149
+
150
+ # TEST: Run validation
151
+ transition_to(:test)
152
+ test_result = run_test_activity
153
+ @last_test_results = test_result
154
+
155
+ if test_result[:all_passing]
156
+ # PASS → DONE
157
+ transition_to(:done)
158
+ log_workflow("completed", iterations: @iteration)
159
+ return {
160
+ success: true,
161
+ iterations: @iteration,
162
+ test_results: test_result
163
+ }
164
+ end
165
+
166
+ # FAIL → DIAGNOSE
167
+ transition_to(:diagnose)
168
+ handle_failure(test_result)
169
+
170
+ # Record checkpoint at intervals
171
+ if (@iteration % @checkpoint_interval).zero?
172
+ record_checkpoint
173
+ end
174
+
175
+ # Reset failure count on progress
176
+ @consecutive_failures = 0 if test_result[:partial_pass]
177
+
178
+ # NEXT_PATCH: Prepare for next iteration
179
+ transition_to(:next_patch)
180
+ prepare_next_iteration(test_result)
181
+
182
+ transition_to(:ready)
183
+ end
184
+ end
185
+
186
+ def transition_to(new_state)
187
+ old_state = @state
188
+ @state = new_state
189
+ log_workflow("state_transition", from: old_state, to: new_state, iteration: @iteration)
190
+ end
191
+
192
+ def create_initial_prompt
193
+ Temporalio::Workflow.execute_activity(
194
+ Activities::CreatePromptActivity,
195
+ {
196
+ project_dir: @project_dir,
197
+ step_name: @step_name,
198
+ step_spec: @step_spec,
199
+ context: @context
200
+ },
201
+ **activity_options(start_to_close_timeout: 120)
202
+ )
203
+ end
204
+
205
+ def run_agent_activity
206
+ instructions = drain_instruction_queue
207
+
208
+ Temporalio::Workflow.execute_activity(
209
+ Activities::RunAgentActivity,
210
+ {
211
+ project_dir: @project_dir,
212
+ step_name: @step_name,
213
+ iteration: @iteration,
214
+ injected_instructions: instructions,
215
+ escalate: @escalate_requested
216
+ },
217
+ **activity_options(
218
+ start_to_close_timeout: 900, # 15 minutes
219
+ heartbeat_timeout: 120 # 2 minutes
220
+ )
221
+ )
222
+ ensure
223
+ @escalate_requested = false
224
+ end
225
+
226
+ def run_test_activity
227
+ Temporalio::Workflow.execute_activity(
228
+ Activities::RunTestsActivity,
229
+ {
230
+ project_dir: @project_dir,
231
+ iteration: @iteration
232
+ },
233
+ **activity_options(
234
+ start_to_close_timeout: 600, # 10 minutes
235
+ heartbeat_timeout: 60
236
+ )
237
+ )
238
+ end
239
+
240
+ def handle_failure(test_result)
241
+ Temporalio::Workflow.execute_activity(
242
+ Activities::DiagnoseFailureActivity,
243
+ {
244
+ project_dir: @project_dir,
245
+ iteration: @iteration,
246
+ test_result: test_result
247
+ },
248
+ **activity_options(start_to_close_timeout: 120)
249
+ )
250
+ end
251
+
252
+ def prepare_next_iteration(test_result)
253
+ Temporalio::Workflow.execute_activity(
254
+ Activities::PrepareNextIterationActivity,
255
+ {
256
+ project_dir: @project_dir,
257
+ iteration: @iteration,
258
+ test_result: test_result,
259
+ failures_only: true
260
+ },
261
+ **activity_options(start_to_close_timeout: 60)
262
+ )
263
+ end
264
+
265
+ def record_checkpoint
266
+ Temporalio::Workflow.execute_activity(
267
+ Activities::RecordCheckpointActivity,
268
+ {
269
+ project_dir: @project_dir,
270
+ step_name: @step_name,
271
+ iteration: @iteration,
272
+ state: @state,
273
+ test_results: @last_test_results
274
+ },
275
+ **activity_options(start_to_close_timeout: 30)
276
+ )
277
+ end
278
+
279
+ def handle_escalation
280
+ log_workflow("escalating_model", consecutive_failures: @consecutive_failures)
281
+ @escalate_requested = true
282
+ @consecutive_failures = 0
283
+ end
284
+
285
+ def drain_instruction_queue
286
+ instructions = @instruction_queue.dup
287
+ @instruction_queue.clear
288
+ instructions
289
+ end
290
+
291
+ def activity_options(overrides = {})
292
+ self.class.activity_options(overrides)
293
+ end
294
+
295
+ def build_result(result)
296
+ {
297
+ status: result[:success] ? "completed" : "failed",
298
+ step_name: @step_name,
299
+ iterations: @iteration,
300
+ success: result[:success],
301
+ reason: result[:reason],
302
+ test_results: result[:test_results],
303
+ started_at: @started_at,
304
+ completed_at: Time.now.iso8601
305
+ }
306
+ end
307
+
308
+ def build_canceled_result
309
+ {
310
+ status: "canceled",
311
+ step_name: @step_name,
312
+ iterations: @iteration,
313
+ state: @state,
314
+ started_at: @started_at,
315
+ completed_at: Time.now.iso8601
316
+ }
317
+ end
318
+ end
319
+ end
320
+ end
321
+ end
@@ -0,0 +1,120 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Temporal integration for AIDP
4
+ # Provides durable workflow orchestration for issue-to-PR pipelines
5
+ #
6
+ # Components:
7
+ # - Connection: Manages Temporal client connections
8
+ # - Worker: Runs workflows and activities
9
+ # - WorkflowClient: Interface for starting/managing workflows
10
+ # - Configuration: Loads Temporal settings from aidp.yml
11
+ #
12
+ # Workflows:
13
+ # - IssueToPrWorkflow: Full issue analysis → implementation → PR pipeline
14
+ # - WorkLoopWorkflow: Fix-forward iteration pattern
15
+ # - SubIssueWorkflow: Child workflow for decomposed tasks
16
+ #
17
+ # Activities:
18
+ # - RunAgentActivity: Execute AI agent
19
+ # - RunTestsActivity: Run tests/linters
20
+ # - AnalyzeIssueActivity: Analyze GitHub issue
21
+ # - CreatePlanActivity: Generate implementation plan
22
+ # - CreatePromptActivity: Build PROMPT.md
23
+ # - DiagnoseFailureActivity: Analyze failures
24
+ # - PrepareNextIterationActivity: Set up next iteration
25
+ # - RecordCheckpointActivity: Save progress
26
+ # - CreatePrActivity: Create GitHub PR
27
+
28
+ module Aidp
29
+ module Temporal
30
+ class << self
31
+ # Get the default configuration
32
+ def configuration(project_dir = Dir.pwd)
33
+ @configurations ||= {}
34
+ @configurations[project_dir] ||= Configuration.new(project_dir)
35
+ end
36
+
37
+ # Check if Temporal is enabled
38
+ def enabled?(project_dir = Dir.pwd)
39
+ configuration(project_dir).enabled?
40
+ end
41
+
42
+ # Build a connection with project configuration
43
+ def connection(project_dir = Dir.pwd)
44
+ configuration(project_dir).build_connection
45
+ end
46
+
47
+ # Build a workflow client with project configuration
48
+ def workflow_client(project_dir = Dir.pwd)
49
+ configuration(project_dir).build_workflow_client
50
+ end
51
+
52
+ # Build a worker with project configuration
53
+ def worker(project_dir = Dir.pwd)
54
+ configuration(project_dir).build_worker
55
+ end
56
+
57
+ # Start a workflow
58
+ def start_workflow(workflow_class, input, project_dir: Dir.pwd, **options)
59
+ client = workflow_client(project_dir)
60
+ client.start_workflow(workflow_class, input, options)
61
+ end
62
+
63
+ # Execute a workflow synchronously
64
+ def execute_workflow(workflow_class, input, project_dir: Dir.pwd, **options)
65
+ client = workflow_client(project_dir)
66
+ client.execute_workflow(workflow_class, input, options)
67
+ end
68
+
69
+ # Get workflow status
70
+ def workflow_status(workflow_id, project_dir: Dir.pwd)
71
+ client = workflow_client(project_dir)
72
+ handle = client.get_workflow(workflow_id)
73
+ handle.describe
74
+ end
75
+
76
+ # Signal a workflow
77
+ def signal_workflow(workflow_id, signal_name, *args, project_dir: Dir.pwd)
78
+ client = workflow_client(project_dir)
79
+ client.signal_workflow(workflow_id, signal_name, *args)
80
+ end
81
+
82
+ # Cancel a workflow
83
+ def cancel_workflow(workflow_id, project_dir: Dir.pwd)
84
+ client = workflow_client(project_dir)
85
+ client.cancel_workflow(workflow_id)
86
+ end
87
+
88
+ # Reset configuration cache (useful for testing)
89
+ def reset!
90
+ @configurations = {}
91
+ end
92
+ end
93
+ end
94
+ end
95
+
96
+ # Require components in dependency order
97
+ require_relative "temporal/configuration"
98
+ require_relative "temporal/connection"
99
+ require_relative "temporal/worker"
100
+ require_relative "temporal/workflow_client"
101
+
102
+ # Require workflows
103
+ require_relative "temporal/workflows/base_workflow"
104
+ require_relative "temporal/workflows/issue_to_pr_workflow"
105
+ require_relative "temporal/workflows/work_loop_workflow"
106
+ require_relative "temporal/workflows/sub_issue_workflow"
107
+
108
+ # Require activities
109
+ require_relative "temporal/activities/base_activity"
110
+ require_relative "temporal/activities/run_agent_activity"
111
+ require_relative "temporal/activities/run_tests_activity"
112
+ require_relative "temporal/activities/analyze_issue_activity"
113
+ require_relative "temporal/activities/create_plan_activity"
114
+ require_relative "temporal/activities/create_prompt_activity"
115
+ require_relative "temporal/activities/diagnose_failure_activity"
116
+ require_relative "temporal/activities/prepare_next_iteration_activity"
117
+ require_relative "temporal/activities/record_checkpoint_activity"
118
+ require_relative "temporal/activities/create_pr_activity"
119
+ require_relative "temporal/activities/run_work_loop_iteration_activity"
120
+ require_relative "temporal/activities/analyze_sub_task_activity"
data/lib/aidp/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Aidp
4
- VERSION = "0.34.1"
4
+ VERSION = "0.39.3"
5
5
  end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aidp
4
+ module Watch
5
+ # Base class for Watch Mode processors
6
+ class BaseProcessor
7
+ def initialize(repository_client:, state_store: nil, label_config: {}, verbose: false)
8
+ @repository_client = repository_client
9
+ @state_store = state_store
10
+ @verbose = verbose
11
+
12
+ # Only use string or symbol access for label_config
13
+ @rebase_label = label_config[:rebase_trigger] ||
14
+ label_config["rebase_trigger"] ||
15
+ "aidp-rebase"
16
+ end
17
+
18
+ # Checks if this processor can handle the current work item
19
+ # @param work_item [WorkItem] The work item to check
20
+ # @return [Boolean] Whether the work item can be processed
21
+ def can_process?(work_item)
22
+ raise NotImplementedError, "Subclasses must implement can_process?"
23
+ end
24
+
25
+ # Process the work item
26
+ # @param work_item [WorkItem] The work item to process
27
+ # @return [Boolean] Whether the work item was processed successfully
28
+ def process(work_item)
29
+ raise NotImplementedError, "Subclasses must implement process"
30
+ end
31
+
32
+ attr_reader :rebase_label
33
+ end
34
+ end
35
+ end
@@ -67,6 +67,17 @@ module Aidp
67
67
 
68
68
  sync_local_aidp_config(working_dir)
69
69
 
70
+ # Check for stuck work loop: work appears done but protocol wasn't followed
71
+ if work_loop_stuck_but_complete?(working_dir: working_dir, base_branch: base_branch)
72
+ display_message("🔧 Detected stuck work loop - work appears complete, bypassing harness", type: :info)
73
+ Aidp.log_info("build_processor", "stuck_work_loop_detected",
74
+ issue: number, working_dir: working_dir)
75
+
76
+ # Skip harness and go directly to success handling
77
+ handle_success(issue: issue, slug: slug, branch_name: branch_name, base_branch: base_branch, plan_data: plan_data, working_dir: working_dir)
78
+ return
79
+ end
80
+
70
81
  prompt_content = build_prompt(issue: issue, plan_data: plan_data)
71
82
  write_prompt(prompt_content, working_dir: working_dir)
72
83
 
@@ -1023,6 +1034,127 @@ module Aidp
1023
1034
  string_key = key.to_s
1024
1035
  plan_data[symbol_key] || plan_data[string_key]
1025
1036
  end
1037
+
1038
+ # Detect if work loop is stuck but work appears complete.
1039
+ # This handles cases where the agent did the work but didn't follow the protocol
1040
+ # (e.g., didn't mark STATUS: COMPLETE in .aidp/PROMPT.md, didn't file tasks).
1041
+ #
1042
+ # Conditions for "stuck but complete":
1043
+ # 1. Has commits ahead of base branch (work was done)
1044
+ # 2. Tests pass (work is valid)
1045
+ # 3. Linters pass (code quality is acceptable)
1046
+ # 4. .aidp/PROMPT.md exists but doesn't have STATUS: COMPLETE (protocol not followed)
1047
+ #
1048
+ # @param working_dir [String] Path to the working directory
1049
+ # @param base_branch [String] Base branch to compare against
1050
+ # @return [Boolean] True if work loop appears stuck but work is complete
1051
+ def work_loop_stuck_but_complete?(working_dir:, base_branch:)
1052
+ Aidp.log_debug("build_processor", "checking_stuck_work_loop", working_dir: working_dir)
1053
+
1054
+ # Check 1: Has commits ahead of base branch
1055
+ unless has_commits_ahead?(working_dir: working_dir, base_branch: base_branch)
1056
+ Aidp.log_debug("build_processor", "stuck_check_no_commits_ahead")
1057
+ return false
1058
+ end
1059
+
1060
+ # Check 2: .aidp/PROMPT.md exists but doesn't have STATUS: COMPLETE
1061
+ prompt_path = File.join(working_dir, ".aidp", "PROMPT.md")
1062
+ if File.exist?(prompt_path)
1063
+ prompt_content = File.read(prompt_path)
1064
+ if prompt_content.match?(/^STATUS:\s*COMPLETE/i)
1065
+ # Protocol was followed, not stuck
1066
+ Aidp.log_debug("build_processor", "stuck_check_already_marked_complete")
1067
+ return false
1068
+ end
1069
+ else
1070
+ # No prompt file means this is a fresh start, not stuck
1071
+ Aidp.log_debug("build_processor", "stuck_check_no_prompt_file")
1072
+ return false
1073
+ end
1074
+
1075
+ # Check 3: Run quick validation (tests + linters)
1076
+ unless quick_validation_passes?(working_dir: working_dir)
1077
+ Aidp.log_debug("build_processor", "stuck_check_validation_failed")
1078
+ return false
1079
+ end
1080
+
1081
+ Aidp.log_info("build_processor", "work_loop_stuck_but_complete",
1082
+ working_dir: working_dir,
1083
+ reason: "has_commits_tests_pass_but_no_status_complete")
1084
+ true
1085
+ rescue => e
1086
+ Aidp.log_warn("build_processor", "stuck_check_error", error: e.message)
1087
+ false
1088
+ end
1089
+
1090
+ # Check if branch has commits ahead of base branch
1091
+ def has_commits_ahead?(working_dir:, base_branch:)
1092
+ Dir.chdir(working_dir) do
1093
+ # Fetch to ensure we have latest refs (use Open3 directly to avoid test mocks)
1094
+ Open3.capture3("git", "fetch", "origin")
1095
+
1096
+ # Count commits ahead of origin/base_branch
1097
+ stdout, _stderr, status = Open3.capture3(
1098
+ "git", "rev-list", "--count", "HEAD", "^origin/#{base_branch}"
1099
+ )
1100
+ return false unless status.success?
1101
+
1102
+ commits_ahead = stdout.strip.to_i
1103
+ Aidp.log_debug("build_processor", "commits_ahead_check",
1104
+ base_branch: base_branch, commits_ahead: commits_ahead)
1105
+ commits_ahead > 0
1106
+ end
1107
+ rescue => e
1108
+ Aidp.log_warn("build_processor", "commits_ahead_check_error", error: e.message)
1109
+ false
1110
+ end
1111
+
1112
+ # Run quick validation to check if tests and linters pass
1113
+ def quick_validation_passes?(working_dir:)
1114
+ Dir.chdir(working_dir) do
1115
+ # Load config to get test/lint commands
1116
+ config_manager = Aidp::Harness::ConfigManager.new(working_dir)
1117
+ harness_config = config_manager.config || {}
1118
+
1119
+ # Try to run tests
1120
+ test_commands = harness_config.dig(:harness, :tests) ||
1121
+ harness_config.dig("harness", "tests") || []
1122
+
1123
+ test_commands.each do |cmd|
1124
+ command = cmd.is_a?(Hash) ? (cmd[:command] || cmd["command"]) : cmd.to_s
1125
+ next if command.nil? || command.empty?
1126
+
1127
+ Aidp.log_debug("build_processor", "quick_validation_running_test", command: command)
1128
+ _stdout, _stderr, status = Open3.capture3(command)
1129
+ unless status.success?
1130
+ Aidp.log_debug("build_processor", "quick_validation_test_failed", command: command)
1131
+ return false
1132
+ end
1133
+ end
1134
+
1135
+ # Try to run linters
1136
+ lint_commands = harness_config.dig(:harness, :linters) ||
1137
+ harness_config.dig("harness", "linters") || []
1138
+
1139
+ lint_commands.each do |cmd|
1140
+ command = cmd.is_a?(Hash) ? (cmd[:command] || cmd["command"]) : cmd.to_s
1141
+ next if command.nil? || command.empty?
1142
+
1143
+ Aidp.log_debug("build_processor", "quick_validation_running_lint", command: command)
1144
+ _stdout, _stderr, status = Open3.capture3(command)
1145
+ unless status.success?
1146
+ Aidp.log_debug("build_processor", "quick_validation_lint_failed", command: command)
1147
+ return false
1148
+ end
1149
+ end
1150
+
1151
+ Aidp.log_debug("build_processor", "quick_validation_passed")
1152
+ true
1153
+ end
1154
+ rescue => e
1155
+ Aidp.log_warn("build_processor", "quick_validation_error", error: e.message)
1156
+ false
1157
+ end
1026
1158
  end
1027
1159
  end
1028
1160
  end
@@ -17,18 +17,10 @@ module Aidp
17
17
  # Pattern for detecting plan proposal comments
18
18
  PLAN_PROPOSAL_PATTERN = /<!-- PLAN_SUMMARY_START -->/i
19
19
 
20
- # Pattern for detecting in-progress label
21
- IN_PROGRESS_LABEL = "aidp-in-progress"
22
-
23
20
  def initialize(repository_client:)
24
21
  @repository_client = repository_client
25
22
  end
26
23
 
27
- # Check if an issue/PR is currently being worked on by another instance
28
- def in_progress?(item)
29
- has_label?(item, IN_PROGRESS_LABEL)
30
- end
31
-
32
24
  # Check if build is already completed for an issue
33
25
  # Looks for completion comment from AIDP
34
26
  def build_completed?(issue)