aidp 0.32.0 ā 0.34.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 +35 -0
- data/lib/aidp/analyze/feature_analyzer.rb +322 -320
- data/lib/aidp/analyze/tree_sitter_scan.rb +3 -0
- data/lib/aidp/auto_update/coordinator.rb +97 -7
- data/lib/aidp/auto_update.rb +0 -12
- data/lib/aidp/cli/devcontainer_commands.rb +0 -5
- data/lib/aidp/cli/eval_command.rb +399 -0
- data/lib/aidp/cli/harness_command.rb +1 -1
- data/lib/aidp/cli/security_command.rb +416 -0
- data/lib/aidp/cli/tools_command.rb +6 -4
- data/lib/aidp/cli.rb +172 -4
- data/lib/aidp/comment_consolidator.rb +78 -0
- data/lib/aidp/concurrency/exec.rb +3 -0
- data/lib/aidp/concurrency.rb +0 -3
- data/lib/aidp/config.rb +113 -1
- data/lib/aidp/config_paths.rb +91 -0
- data/lib/aidp/daemon/runner.rb +8 -4
- data/lib/aidp/errors.rb +134 -0
- data/lib/aidp/evaluations/context_capture.rb +205 -0
- data/lib/aidp/evaluations/evaluation_record.rb +114 -0
- data/lib/aidp/evaluations/evaluation_storage.rb +250 -0
- data/lib/aidp/evaluations.rb +23 -0
- data/lib/aidp/execute/async_work_loop_runner.rb +4 -1
- data/lib/aidp/execute/interactive_repl.rb +6 -2
- data/lib/aidp/execute/prompt_evaluator.rb +359 -0
- data/lib/aidp/execute/repl_macros.rb +100 -1
- data/lib/aidp/execute/work_loop_runner.rb +719 -58
- data/lib/aidp/execute/work_loop_state.rb +4 -1
- data/lib/aidp/execute/workflow_selector.rb +3 -0
- data/lib/aidp/harness/ai_decision_engine.rb +79 -0
- data/lib/aidp/harness/ai_filter_factory.rb +285 -0
- data/lib/aidp/harness/capability_registry.rb +2 -0
- data/lib/aidp/harness/condition_detector.rb +3 -0
- data/lib/aidp/harness/config_loader.rb +3 -0
- data/lib/aidp/harness/config_schema.rb +97 -1
- data/lib/aidp/harness/config_validator.rb +1 -1
- data/lib/aidp/harness/configuration.rb +61 -5
- data/lib/aidp/harness/enhanced_runner.rb +14 -11
- data/lib/aidp/harness/error_handler.rb +3 -0
- data/lib/aidp/harness/filter_definition.rb +212 -0
- data/lib/aidp/harness/generated_filter_strategy.rb +197 -0
- data/lib/aidp/harness/output_filter.rb +50 -25
- data/lib/aidp/harness/output_filter_config.rb +129 -0
- data/lib/aidp/harness/provider_factory.rb +3 -0
- data/lib/aidp/harness/provider_manager.rb +96 -2
- data/lib/aidp/harness/runner.rb +5 -12
- data/lib/aidp/harness/state/persistence.rb +3 -0
- data/lib/aidp/harness/state_manager.rb +3 -0
- data/lib/aidp/harness/status_display.rb +28 -20
- data/lib/aidp/harness/test_runner.rb +179 -41
- data/lib/aidp/harness/thinking_depth_manager.rb +44 -28
- data/lib/aidp/harness/ui/enhanced_tui.rb +4 -0
- data/lib/aidp/harness/ui/enhanced_workflow_selector.rb +4 -0
- data/lib/aidp/harness/ui/error_handler.rb +3 -0
- data/lib/aidp/harness/ui/job_monitor.rb +4 -0
- data/lib/aidp/harness/ui/navigation/submenu.rb +2 -2
- data/lib/aidp/harness/ui/navigation/workflow_selector.rb +6 -0
- data/lib/aidp/harness/ui/spinner_helper.rb +3 -0
- data/lib/aidp/harness/ui/workflow_controller.rb +3 -0
- data/lib/aidp/harness/user_interface.rb +3 -0
- data/lib/aidp/loader.rb +195 -0
- data/lib/aidp/logger.rb +3 -0
- data/lib/aidp/message_display.rb +31 -0
- data/lib/aidp/metadata/compiler.rb +29 -17
- data/lib/aidp/metadata/query.rb +1 -1
- data/lib/aidp/metadata/scanner.rb +8 -1
- data/lib/aidp/metadata/tool_metadata.rb +13 -13
- data/lib/aidp/metadata/validator.rb +10 -0
- data/lib/aidp/metadata.rb +16 -0
- data/lib/aidp/pr_worktree_manager.rb +20 -8
- data/lib/aidp/provider_manager.rb +4 -7
- data/lib/aidp/providers/base.rb +2 -0
- data/lib/aidp/security/rule_of_two_enforcer.rb +210 -0
- data/lib/aidp/security/secrets_proxy.rb +328 -0
- data/lib/aidp/security/secrets_registry.rb +227 -0
- data/lib/aidp/security/trifecta_state.rb +220 -0
- data/lib/aidp/security/watch_mode_handler.rb +306 -0
- data/lib/aidp/security/work_loop_adapter.rb +277 -0
- data/lib/aidp/security.rb +56 -0
- data/lib/aidp/setup/wizard.rb +283 -11
- data/lib/aidp/skills.rb +0 -5
- data/lib/aidp/storage/csv_storage.rb +3 -0
- data/lib/aidp/style_guide/selector.rb +360 -0
- data/lib/aidp/tooling_detector.rb +283 -16
- data/lib/aidp/version.rb +1 -1
- data/lib/aidp/watch/auto_merger.rb +274 -0
- data/lib/aidp/watch/auto_pr_processor.rb +125 -7
- data/lib/aidp/watch/build_processor.rb +16 -1
- data/lib/aidp/watch/change_request_processor.rb +682 -150
- data/lib/aidp/watch/ci_fix_processor.rb +262 -4
- data/lib/aidp/watch/feedback_collector.rb +191 -0
- data/lib/aidp/watch/hierarchical_pr_strategy.rb +256 -0
- data/lib/aidp/watch/implementation_verifier.rb +142 -1
- data/lib/aidp/watch/plan_generator.rb +70 -13
- data/lib/aidp/watch/plan_processor.rb +12 -5
- data/lib/aidp/watch/projects_processor.rb +286 -0
- data/lib/aidp/watch/repository_client.rb +871 -22
- data/lib/aidp/watch/review_processor.rb +33 -6
- data/lib/aidp/watch/runner.rb +80 -29
- data/lib/aidp/watch/state_store.rb +233 -0
- data/lib/aidp/watch/sub_issue_creator.rb +221 -0
- data/lib/aidp/watch.rb +5 -7
- data/lib/aidp/workflows/guided_agent.rb +4 -0
- data/lib/aidp/workstream_cleanup.rb +0 -2
- data/lib/aidp/workstream_executor.rb +3 -4
- data/lib/aidp/worktree.rb +61 -12
- data/lib/aidp/worktree_branch_manager.rb +347 -101
- data/lib/aidp.rb +21 -106
- data/templates/implementation/iterative_implementation.md +46 -3
- metadata +91 -36
- data/lib/aidp/config/paths.rb +0 -131
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "prompt_manager"
|
|
4
|
+
require_relative "prompt_evaluator"
|
|
4
5
|
require_relative "checkpoint"
|
|
5
6
|
require_relative "checkpoint_display"
|
|
6
7
|
require_relative "guard_policy"
|
|
@@ -10,6 +11,8 @@ require_relative "agent_signal_parser"
|
|
|
10
11
|
require_relative "steps"
|
|
11
12
|
require_relative "../harness/test_runner"
|
|
12
13
|
require_relative "../errors"
|
|
14
|
+
require_relative "../style_guide/selector"
|
|
15
|
+
require_relative "../security"
|
|
13
16
|
|
|
14
17
|
module Aidp
|
|
15
18
|
module Execute
|
|
@@ -38,7 +41,10 @@ module Aidp
|
|
|
38
41
|
}.freeze
|
|
39
42
|
include Aidp::MessageDisplay
|
|
40
43
|
|
|
41
|
-
|
|
44
|
+
# Expose state for testability
|
|
45
|
+
attr_accessor :iteration_count, :step_name, :options, :persistent_tasklist
|
|
46
|
+
attr_reader :project_dir, :current_state, :state_history, :test_runner, :prompt_manager, :checkpoint
|
|
47
|
+
attr_writer :guard_policy, :prompt_manager, :style_guide_selector
|
|
42
48
|
|
|
43
49
|
MAX_ITERATIONS = 50 # Safety limit
|
|
44
50
|
CHECKPOINT_INTERVAL = 5 # Record checkpoint every N iterations
|
|
@@ -54,6 +60,7 @@ module Aidp
|
|
|
54
60
|
@checkpoint = Checkpoint.new(project_dir)
|
|
55
61
|
@checkpoint_display = CheckpointDisplay.new(prompt: @prompt)
|
|
56
62
|
@guard_policy = GuardPolicy.new(project_dir, config.guards_config)
|
|
63
|
+
@work_context = {}
|
|
57
64
|
@persistent_tasklist = PersistentTasklist.new(project_dir)
|
|
58
65
|
@iteration_count = 0
|
|
59
66
|
@step_name = nil
|
|
@@ -65,9 +72,18 @@ module Aidp
|
|
|
65
72
|
|
|
66
73
|
# Initialize thinking depth manager for intelligent model selection
|
|
67
74
|
require_relative "../harness/thinking_depth_manager"
|
|
68
|
-
@thinking_depth_manager = options[:thinking_depth_manager] || Aidp::Harness::ThinkingDepthManager.new(config)
|
|
75
|
+
@thinking_depth_manager = options[:thinking_depth_manager] || Aidp::Harness::ThinkingDepthManager.new(config, root_dir: @project_dir)
|
|
69
76
|
@consecutive_failures = 0
|
|
70
77
|
@last_tier = nil
|
|
78
|
+
|
|
79
|
+
# Initialize style guide selector for intelligent section selection
|
|
80
|
+
@style_guide_selector = options[:style_guide_selector] || Aidp::StyleGuide::Selector.new(project_dir: project_dir)
|
|
81
|
+
|
|
82
|
+
# FIX for issue #391: Initialize prompt evaluator for iteration threshold assessment
|
|
83
|
+
@prompt_evaluator = options[:prompt_evaluator] || PromptEvaluator.new(config)
|
|
84
|
+
|
|
85
|
+
# Initialize security adapter for Rule of Two enforcement
|
|
86
|
+
@security_adapter = options[:security_adapter] || Aidp::Security::WorkLoopAdapter.new(project_dir: project_dir)
|
|
71
87
|
end
|
|
72
88
|
|
|
73
89
|
# Execute a step using fix-forward work loop pattern
|
|
@@ -75,6 +91,7 @@ module Aidp
|
|
|
75
91
|
# Never rolls back - only moves forward through fixes
|
|
76
92
|
def execute_step(step_name, step_spec, context = {})
|
|
77
93
|
@step_name = step_name
|
|
94
|
+
@work_context = context
|
|
78
95
|
@iteration_count = 0
|
|
79
96
|
transition_to(:ready)
|
|
80
97
|
|
|
@@ -137,6 +154,11 @@ module Aidp
|
|
|
137
154
|
@current_state = :ready
|
|
138
155
|
@state_history.clear
|
|
139
156
|
|
|
157
|
+
# Begin security tracking for this agentic work unit
|
|
158
|
+
work_unit_id = "agentic_#{@step_name}_#{SecureRandom.hex(4)}"
|
|
159
|
+
@security_adapter.begin_work_unit(work_unit_id: work_unit_id, context: context)
|
|
160
|
+
display_security_status
|
|
161
|
+
|
|
140
162
|
create_initial_prompt(step_spec, context)
|
|
141
163
|
|
|
142
164
|
loop do
|
|
@@ -148,6 +170,10 @@ module Aidp
|
|
|
148
170
|
display_message("ā ļø Max iterations (#{MAX_ITERATIONS}) reached for #{@step_name}", type: :warning)
|
|
149
171
|
display_state_summary
|
|
150
172
|
archive_and_cleanup
|
|
173
|
+
|
|
174
|
+
# End security tracking for this work unit
|
|
175
|
+
@security_adapter.end_work_unit
|
|
176
|
+
|
|
151
177
|
return build_agentic_payload(
|
|
152
178
|
agent_result: nil,
|
|
153
179
|
response: build_max_iterations_result,
|
|
@@ -161,13 +187,57 @@ module Aidp
|
|
|
161
187
|
|
|
162
188
|
transition_to(:apply_patch)
|
|
163
189
|
|
|
190
|
+
# Preview provider/model selection and queued checks for this iteration
|
|
191
|
+
preview_provider, preview_model, _model_data = select_model_for_current_tier
|
|
192
|
+
prompt_length = @prompt_manager.read&.length || 0
|
|
193
|
+
checks_summary = planned_checks_summary
|
|
194
|
+
display_iteration_overview(preview_provider, preview_model, prompt_length, checks_summary)
|
|
195
|
+
log_iteration_status("running",
|
|
196
|
+
provider: preview_provider,
|
|
197
|
+
model: preview_model,
|
|
198
|
+
prompt_length: prompt_length,
|
|
199
|
+
checks: checks_summary)
|
|
200
|
+
|
|
201
|
+
# Check security policy before agent call (Rule of Two enforcement)
|
|
202
|
+
# Agent calls enable egress capability
|
|
203
|
+
begin
|
|
204
|
+
@security_adapter.check_agent_call_allowed!(operation: :agent_execution)
|
|
205
|
+
rescue Aidp::Security::PolicyViolation => e
|
|
206
|
+
# Security policy violation - cannot proceed with agent call
|
|
207
|
+
Aidp.logger.error("work_loop", "Security policy violation",
|
|
208
|
+
step: @step_name,
|
|
209
|
+
iteration: @iteration_count,
|
|
210
|
+
error: e.message)
|
|
211
|
+
display_message(" š”ļø Security policy violation: #{e.message}", type: :error)
|
|
212
|
+
display_message(" Cannot proceed - Rule of Two would be violated", type: :error)
|
|
213
|
+
|
|
214
|
+
# End security tracking and return error
|
|
215
|
+
@security_adapter.end_work_unit
|
|
216
|
+
return build_agentic_payload(
|
|
217
|
+
agent_result: nil,
|
|
218
|
+
response: {status: "error", message: "Security policy violation: #{e.message}"},
|
|
219
|
+
summary: nil,
|
|
220
|
+
completed: false,
|
|
221
|
+
terminate: true
|
|
222
|
+
)
|
|
223
|
+
end
|
|
224
|
+
|
|
164
225
|
# Wrap agent call in exception handling for true fix-forward
|
|
165
226
|
begin
|
|
166
|
-
agent_result = apply_patch
|
|
227
|
+
agent_result = apply_patch(preview_provider, preview_model)
|
|
167
228
|
rescue Aidp::Errors::ConfigurationError
|
|
168
229
|
# Configuration errors should crash immediately (crash-early principle)
|
|
169
230
|
# Re-raise without catching
|
|
170
231
|
raise
|
|
232
|
+
rescue Aidp::Security::PolicyViolation => e
|
|
233
|
+
# Security violations should not continue - they are policy failures
|
|
234
|
+
Aidp.logger.error("work_loop", "Security policy violation during agent call",
|
|
235
|
+
step: @step_name,
|
|
236
|
+
iteration: @iteration_count,
|
|
237
|
+
error: e.message)
|
|
238
|
+
display_message(" š”ļø Security violation: #{e.message}", type: :error)
|
|
239
|
+
@security_adapter.end_work_unit
|
|
240
|
+
raise
|
|
171
241
|
rescue => e
|
|
172
242
|
# Convert exception to error result for fix-forward handling
|
|
173
243
|
Aidp.logger.error("work_loop", "Exception during agent call",
|
|
@@ -246,20 +316,48 @@ module Aidp
|
|
|
246
316
|
|
|
247
317
|
# Check task completion status
|
|
248
318
|
task_completion_result = check_task_completion
|
|
319
|
+
agent_completed = agent_marked_complete?(agent_result)
|
|
320
|
+
|
|
321
|
+
# FIX for issue #391: Comprehensive logging at completion decision point
|
|
322
|
+
Aidp.log_debug("work_loop", "completion_decision_point",
|
|
323
|
+
iteration: @iteration_count,
|
|
324
|
+
all_checks_pass: all_checks_pass,
|
|
325
|
+
agent_marked_complete: agent_completed,
|
|
326
|
+
task_completion_complete: task_completion_result[:complete],
|
|
327
|
+
task_completion_reason: task_completion_result[:reason],
|
|
328
|
+
test_success: test_results[:success],
|
|
329
|
+
lint_success: lint_results[:success],
|
|
330
|
+
formatter_success: formatter_results[:success],
|
|
331
|
+
build_success: build_results[:success],
|
|
332
|
+
doc_success: doc_results[:success])
|
|
249
333
|
|
|
250
334
|
if all_checks_pass
|
|
251
335
|
transition_to(:pass)
|
|
252
336
|
|
|
253
|
-
if
|
|
337
|
+
if agent_completed
|
|
254
338
|
# Check if tasks are complete
|
|
255
339
|
if task_completion_result[:complete]
|
|
340
|
+
Aidp.log_debug("work_loop", "completion_approved",
|
|
341
|
+
iteration: @iteration_count,
|
|
342
|
+
reason: task_completion_result[:reason])
|
|
343
|
+
|
|
256
344
|
transition_to(:done)
|
|
257
345
|
record_final_checkpoint(all_results)
|
|
258
346
|
display_task_summary
|
|
259
347
|
display_message("ā
Step #{@step_name} completed after #{@iteration_count} iterations", type: :success)
|
|
260
348
|
display_state_summary
|
|
349
|
+
log_iteration_status("completed",
|
|
350
|
+
provider: preview_provider,
|
|
351
|
+
model: preview_model,
|
|
352
|
+
prompt_length: prompt_length,
|
|
353
|
+
checks: checks_summary,
|
|
354
|
+
task_status: "complete",
|
|
355
|
+
completion_reason: task_completion_result[:reason])
|
|
261
356
|
archive_and_cleanup
|
|
262
357
|
|
|
358
|
+
# End security tracking for this work unit
|
|
359
|
+
@security_adapter.end_work_unit
|
|
360
|
+
|
|
263
361
|
return build_agentic_payload(
|
|
264
362
|
agent_result: agent_result,
|
|
265
363
|
response: build_success_result(agent_result),
|
|
@@ -269,16 +367,36 @@ module Aidp
|
|
|
269
367
|
)
|
|
270
368
|
else
|
|
271
369
|
# All checks passed but tasks not complete
|
|
370
|
+
Aidp.log_debug("work_loop", "completion_blocked_tasks_incomplete",
|
|
371
|
+
iteration: @iteration_count,
|
|
372
|
+
reason: task_completion_result[:reason],
|
|
373
|
+
message: task_completion_result[:message])
|
|
374
|
+
|
|
272
375
|
display_message(" All checks passed but tasks not complete", type: :warning)
|
|
273
376
|
display_message(" #{task_completion_result[:message]}", type: :warning)
|
|
274
377
|
display_task_summary
|
|
378
|
+
log_iteration_status("checks_passed_tasks_incomplete",
|
|
379
|
+
provider: preview_provider,
|
|
380
|
+
model: preview_model,
|
|
381
|
+
prompt_length: prompt_length,
|
|
382
|
+
checks: checks_summary,
|
|
383
|
+
task_status: "incomplete",
|
|
384
|
+
task_completion_reason: task_completion_result[:reason])
|
|
275
385
|
transition_to(:next_patch)
|
|
276
386
|
|
|
277
387
|
# Append task completion requirement to PROMPT.md
|
|
278
388
|
append_task_requirement_to_prompt(task_completion_result[:message])
|
|
279
389
|
end
|
|
280
390
|
else
|
|
391
|
+
Aidp.log_debug("work_loop", "completion_blocked_agent_not_complete",
|
|
392
|
+
iteration: @iteration_count)
|
|
393
|
+
|
|
281
394
|
display_message(" All checks passed but work not marked complete", type: :info)
|
|
395
|
+
log_iteration_status("checks_passed_waiting_agent_completion",
|
|
396
|
+
provider: preview_provider,
|
|
397
|
+
model: preview_model,
|
|
398
|
+
prompt_length: prompt_length,
|
|
399
|
+
checks: checks_summary)
|
|
282
400
|
transition_to(:next_patch)
|
|
283
401
|
end
|
|
284
402
|
else
|
|
@@ -289,9 +407,154 @@ module Aidp
|
|
|
289
407
|
diagnostic = diagnose_failures(all_results)
|
|
290
408
|
|
|
291
409
|
transition_to(:next_patch)
|
|
410
|
+
log_iteration_status("checks_failed",
|
|
411
|
+
provider: preview_provider,
|
|
412
|
+
model: preview_model,
|
|
413
|
+
prompt_length: prompt_length,
|
|
414
|
+
checks: checks_summary,
|
|
415
|
+
failures: failure_summary_for_log(all_results))
|
|
292
416
|
prepare_next_iteration(all_results, diagnostic)
|
|
293
417
|
end
|
|
418
|
+
|
|
419
|
+
# FIX for issue #391: Evaluate prompt effectiveness at iteration thresholds
|
|
420
|
+
# After 10+ iterations, assess whether the prompt is leading to progress
|
|
421
|
+
evaluate_prompt_effectiveness(all_results)
|
|
422
|
+
end
|
|
423
|
+
end
|
|
424
|
+
|
|
425
|
+
# Evaluate prompt effectiveness at iteration thresholds
|
|
426
|
+
# FIX for issue #391: Provides feedback when work loop is stuck
|
|
427
|
+
# Note: Errors during evaluation are logged but don't fail the work loop
|
|
428
|
+
def evaluate_prompt_effectiveness(all_results)
|
|
429
|
+
return unless @prompt_evaluator.should_evaluate?(@iteration_count)
|
|
430
|
+
|
|
431
|
+
Aidp.log_debug("work_loop", "evaluating_prompt_effectiveness",
|
|
432
|
+
iteration: @iteration_count)
|
|
433
|
+
|
|
434
|
+
display_message("š Evaluating prompt effectiveness (iteration #{@iteration_count})...", type: :info)
|
|
435
|
+
|
|
436
|
+
task_summary = build_task_summary_for_evaluation
|
|
437
|
+
prompt_content = @prompt_manager.read
|
|
438
|
+
|
|
439
|
+
evaluation = @prompt_evaluator.evaluate(
|
|
440
|
+
prompt_content: prompt_content,
|
|
441
|
+
iteration_count: @iteration_count,
|
|
442
|
+
task_summary: task_summary,
|
|
443
|
+
recent_failures: all_results,
|
|
444
|
+
step_name: @step_name
|
|
445
|
+
)
|
|
446
|
+
|
|
447
|
+
display_prompt_evaluation_results(evaluation)
|
|
448
|
+
|
|
449
|
+
# If prompt is deemed ineffective, append suggestions to PROMPT.md
|
|
450
|
+
unless evaluation[:effective]
|
|
451
|
+
append_evaluation_feedback_to_prompt(evaluation)
|
|
452
|
+
end
|
|
453
|
+
|
|
454
|
+
Aidp.log_info("work_loop", "prompt_evaluation_complete",
|
|
455
|
+
iteration: @iteration_count,
|
|
456
|
+
effective: evaluation[:effective],
|
|
457
|
+
confidence: evaluation[:confidence])
|
|
458
|
+
rescue => e
|
|
459
|
+
# Don't let evaluation errors break the work loop
|
|
460
|
+
Aidp.log_warn("work_loop", "prompt_evaluation_error",
|
|
461
|
+
iteration: @iteration_count,
|
|
462
|
+
error: e.message,
|
|
463
|
+
error_class: e.class.name)
|
|
464
|
+
display_message(" ā ļø Prompt evaluation skipped due to error: #{e.message}", type: :muted)
|
|
465
|
+
end
|
|
466
|
+
|
|
467
|
+
def build_task_summary_for_evaluation
|
|
468
|
+
all_tasks = @persistent_tasklist.all
|
|
469
|
+
return {} if all_tasks.empty?
|
|
470
|
+
|
|
471
|
+
{
|
|
472
|
+
total: all_tasks.size,
|
|
473
|
+
done: all_tasks.count { |t| t.status == :done },
|
|
474
|
+
in_progress: all_tasks.count { |t| t.status == :in_progress },
|
|
475
|
+
pending: all_tasks.count { |t| t.status == :pending },
|
|
476
|
+
abandoned: all_tasks.count { |t| t.status == :abandoned }
|
|
477
|
+
}
|
|
478
|
+
end
|
|
479
|
+
|
|
480
|
+
def display_prompt_evaluation_results(evaluation)
|
|
481
|
+
# Skip display if evaluation was skipped
|
|
482
|
+
if evaluation[:skipped]
|
|
483
|
+
display_message(" ā¹ļø Prompt evaluation skipped: #{evaluation[:skip_reason]}", type: :muted)
|
|
484
|
+
return
|
|
485
|
+
end
|
|
486
|
+
|
|
487
|
+
if evaluation[:effective]
|
|
488
|
+
display_message(" ā
Prompt appears effective, continuing...", type: :success)
|
|
489
|
+
else
|
|
490
|
+
display_message(" ā ļø Prompt may need improvement:", type: :warning)
|
|
491
|
+
|
|
492
|
+
if evaluation[:issues]&.any?
|
|
493
|
+
display_message(" Issues identified:", type: :info)
|
|
494
|
+
evaluation[:issues].each { |issue| display_message(" - #{issue}", type: :warning) }
|
|
495
|
+
end
|
|
496
|
+
|
|
497
|
+
if evaluation[:suggestions]&.any?
|
|
498
|
+
display_message(" Suggestions:", type: :info)
|
|
499
|
+
evaluation[:suggestions].take(3).each { |s| display_message(" - #{s}", type: :info) }
|
|
500
|
+
end
|
|
501
|
+
|
|
502
|
+
if evaluation[:likely_blockers]&.any?
|
|
503
|
+
display_message(" Likely blockers:", type: :warning)
|
|
504
|
+
evaluation[:likely_blockers].each { |b| display_message(" - #{b}", type: :error) }
|
|
505
|
+
end
|
|
506
|
+
end
|
|
507
|
+
|
|
508
|
+
display_message(" Confidence: #{(evaluation[:confidence] * 100).round}%", type: :muted)
|
|
509
|
+
end
|
|
510
|
+
|
|
511
|
+
def append_evaluation_feedback_to_prompt(evaluation)
|
|
512
|
+
feedback_section = build_evaluation_feedback_section(evaluation)
|
|
513
|
+
|
|
514
|
+
@prompt_manager.append(feedback_section)
|
|
515
|
+
|
|
516
|
+
Aidp.log_debug("work_loop", "appended_evaluation_feedback",
|
|
517
|
+
iteration: @iteration_count,
|
|
518
|
+
feedback_size: feedback_section.length)
|
|
519
|
+
end
|
|
520
|
+
|
|
521
|
+
def build_evaluation_feedback_section(evaluation)
|
|
522
|
+
parts = []
|
|
523
|
+
parts << "\n\n## ā ļø Work Loop Progress Assessment (Iteration #{@iteration_count})"
|
|
524
|
+
parts << ""
|
|
525
|
+
parts << "The work loop has been running for #{@iteration_count} iterations without completion."
|
|
526
|
+
parts << "An automated assessment identified the following:"
|
|
527
|
+
parts << ""
|
|
528
|
+
|
|
529
|
+
if evaluation[:issues]&.any?
|
|
530
|
+
parts << "### Issues Identified"
|
|
531
|
+
evaluation[:issues].each { |i| parts << "- #{i}" }
|
|
532
|
+
parts << ""
|
|
533
|
+
end
|
|
534
|
+
|
|
535
|
+
if evaluation[:suggestions]&.any?
|
|
536
|
+
parts << "### Suggestions for Progress"
|
|
537
|
+
evaluation[:suggestions].each { |s| parts << "- #{s}" }
|
|
538
|
+
parts << ""
|
|
539
|
+
end
|
|
540
|
+
|
|
541
|
+
if evaluation[:recommended_actions]&.any?
|
|
542
|
+
parts << "### Recommended Actions"
|
|
543
|
+
evaluation[:recommended_actions].each do |action|
|
|
544
|
+
parts << "- [#{action[:priority]&.upcase || "MEDIUM"}] #{action[:action]}"
|
|
545
|
+
parts << " Rationale: #{action[:rationale]}" if action[:rationale]
|
|
546
|
+
end
|
|
547
|
+
parts << ""
|
|
294
548
|
end
|
|
549
|
+
|
|
550
|
+
parts << "### Next Steps"
|
|
551
|
+
parts << "Please address the above issues and either:"
|
|
552
|
+
parts << "1. Complete the remaining work and mark STATUS: COMPLETE"
|
|
553
|
+
parts << "2. File tasks for remaining work and complete them systematically"
|
|
554
|
+
parts << "3. If blocked, explain the blocker clearly in your response"
|
|
555
|
+
parts << ""
|
|
556
|
+
|
|
557
|
+
parts.join("\n")
|
|
295
558
|
end
|
|
296
559
|
|
|
297
560
|
def run_decider_agentic_unit(context)
|
|
@@ -499,8 +762,8 @@ module Aidp
|
|
|
499
762
|
end
|
|
500
763
|
|
|
501
764
|
# Apply patch - send PROMPT.md to agent
|
|
502
|
-
def apply_patch
|
|
503
|
-
send_to_agent
|
|
765
|
+
def apply_patch(selected_provider = nil, selected_model = nil)
|
|
766
|
+
send_to_agent(selected_provider: selected_provider, selected_model: selected_model)
|
|
504
767
|
end
|
|
505
768
|
|
|
506
769
|
# Check if agent marked work complete
|
|
@@ -549,7 +812,9 @@ module Aidp
|
|
|
549
812
|
# Traditional prompt building (fallback or when optimization disabled)
|
|
550
813
|
template_content = load_template(step_spec["templates"]&.first)
|
|
551
814
|
prd_content = load_prd
|
|
552
|
-
|
|
815
|
+
# Use provider-aware style guide loading - skips for Claude/Copilot,
|
|
816
|
+
# selects relevant STYLE_GUIDE sections for other providers
|
|
817
|
+
style_guide = load_style_guide_for_provider(context)
|
|
553
818
|
user_input = format_user_input(context[:user_input])
|
|
554
819
|
deterministic_outputs = Array(context[:deterministic_outputs])
|
|
555
820
|
previous_summary = context[:previous_agent_summary]
|
|
@@ -729,7 +994,7 @@ module Aidp
|
|
|
729
994
|
parts.join("\n")
|
|
730
995
|
end
|
|
731
996
|
|
|
732
|
-
def send_to_agent
|
|
997
|
+
def send_to_agent(selected_provider: nil, selected_model: nil)
|
|
733
998
|
prompt_content = @prompt_manager.read
|
|
734
999
|
return {status: "error", message: "PROMPT.md not found"} unless prompt_content
|
|
735
1000
|
|
|
@@ -737,9 +1002,11 @@ module Aidp
|
|
|
737
1002
|
full_prompt = build_work_loop_header(@step_name, @iteration_count) + "\n\n" + prompt_content
|
|
738
1003
|
|
|
739
1004
|
# Select model based on thinking depth tier
|
|
740
|
-
provider_name
|
|
1005
|
+
provider_name = selected_provider
|
|
1006
|
+
model_name = selected_model
|
|
1007
|
+
provider_name, model_name, _model_data = select_model_for_current_tier if provider_name.nil? || model_name.nil?
|
|
741
1008
|
|
|
742
|
-
if provider_name.nil?
|
|
1009
|
+
if provider_name.nil?
|
|
743
1010
|
Aidp.logger.error("work_loop", "Failed to select model for tier",
|
|
744
1011
|
tier: @thinking_depth_manager.current_tier,
|
|
745
1012
|
step: @step_name,
|
|
@@ -750,39 +1017,213 @@ module Aidp
|
|
|
750
1017
|
# Log model selection
|
|
751
1018
|
tier = @thinking_depth_manager.current_tier
|
|
752
1019
|
if @last_tier != tier
|
|
753
|
-
|
|
1020
|
+
model_label = model_name || "auto"
|
|
1021
|
+
display_message(" š” Using tier: #{tier} (#{provider_name}/#{model_label})", type: :info)
|
|
754
1022
|
@last_tier = tier
|
|
755
1023
|
end
|
|
756
1024
|
|
|
757
1025
|
# CRITICAL: Change to project directory before calling provider
|
|
758
1026
|
# This ensures Claude CLI runs in the correct directory and can create files
|
|
759
1027
|
Dir.chdir(@project_dir) do
|
|
760
|
-
#
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
1028
|
+
# Execute with sanitized environment (secrets stripped) when security is enabled
|
|
1029
|
+
# This ensures agent processes cannot access registered secrets directly
|
|
1030
|
+
execute_block = lambda do
|
|
1031
|
+
@provider_manager.execute_with_provider(
|
|
1032
|
+
provider_name,
|
|
1033
|
+
full_prompt,
|
|
1034
|
+
{
|
|
1035
|
+
step_name: @step_name,
|
|
1036
|
+
iteration: @iteration_count,
|
|
1037
|
+
project_dir: @project_dir,
|
|
1038
|
+
model: model_name,
|
|
1039
|
+
tier: @thinking_depth_manager.current_tier
|
|
1040
|
+
}
|
|
1041
|
+
)
|
|
1042
|
+
end
|
|
1043
|
+
|
|
1044
|
+
if @security_adapter.enabled?
|
|
1045
|
+
@security_adapter.with_sanitized_environment(&execute_block)
|
|
1046
|
+
else
|
|
1047
|
+
execute_block.call
|
|
1048
|
+
end
|
|
1049
|
+
end
|
|
1050
|
+
end
|
|
1051
|
+
|
|
1052
|
+
def display_iteration_overview(provider_name, model_name, prompt_length, checks_summary = nil)
|
|
1053
|
+
tier = @thinking_depth_manager.current_tier
|
|
1054
|
+
checks = checks_summary
|
|
1055
|
+
checks ||= summarize_checks(@test_runner.planned_commands) if @test_runner.respond_to?(:planned_commands)
|
|
1056
|
+
model_label = model_name || "auto"
|
|
1057
|
+
context_labels = iteration_context_labels
|
|
1058
|
+
|
|
1059
|
+
display_message(" ⢠Step: #{@step_name} | Tier: #{tier} | Model: #{provider_name}/#{model_label}", type: :info)
|
|
1060
|
+
display_message(" ⢠Prompt size: #{prompt_length} chars | State: #{STATES[@current_state]}", type: :info)
|
|
1061
|
+
display_message(" ⢠Upcoming checks: #{checks}", type: :info) if checks && !checks.empty?
|
|
1062
|
+
display_message(" ⢠Context: #{context_labels.join(" | ")}", type: :info) if context_labels.any?
|
|
1063
|
+
|
|
1064
|
+
# Display output filtering configuration if enabled
|
|
1065
|
+
filtering_info = summarize_output_filtering
|
|
1066
|
+
display_message(" ⢠Output filtering: #{filtering_info}", type: :info) if filtering_info
|
|
1067
|
+
end
|
|
1068
|
+
|
|
1069
|
+
# Summarize output filtering configuration
|
|
1070
|
+
def summarize_output_filtering
|
|
1071
|
+
return nil unless @config.respond_to?(:output_filtering_enabled?) && @config.output_filtering_enabled?
|
|
1072
|
+
|
|
1073
|
+
iteration = @test_runner.respond_to?(:iteration_count) ? @test_runner.iteration_count : 0
|
|
1074
|
+
|
|
1075
|
+
test_mode = if @config.respond_to?(:test_output_mode)
|
|
1076
|
+
@config.test_output_mode
|
|
1077
|
+
elsif iteration > 1
|
|
1078
|
+
:failures_only
|
|
1079
|
+
else
|
|
1080
|
+
:full
|
|
1081
|
+
end
|
|
1082
|
+
|
|
1083
|
+
lint_mode = if @config.respond_to?(:lint_output_mode)
|
|
1084
|
+
@config.lint_output_mode
|
|
1085
|
+
elsif iteration > 1
|
|
1086
|
+
:failures_only
|
|
1087
|
+
else
|
|
1088
|
+
:full
|
|
1089
|
+
end
|
|
1090
|
+
|
|
1091
|
+
if test_mode == :full && lint_mode == :full
|
|
1092
|
+
nil # Don't show message when no filtering is active
|
|
1093
|
+
else
|
|
1094
|
+
"test=#{test_mode}, lint=#{lint_mode}"
|
|
1095
|
+
end
|
|
1096
|
+
rescue
|
|
1097
|
+
nil
|
|
1098
|
+
end
|
|
1099
|
+
|
|
1100
|
+
# Display output filtering statistics after test/lint runs
|
|
1101
|
+
def display_filtering_stats
|
|
1102
|
+
return unless @test_runner.respond_to?(:filter_stats)
|
|
1103
|
+
|
|
1104
|
+
stats = @test_runner.filter_stats
|
|
1105
|
+
return if stats[:total_input_bytes].zero?
|
|
1106
|
+
|
|
1107
|
+
reduction = ((stats[:total_input_bytes] - stats[:total_output_bytes]).to_f / stats[:total_input_bytes] * 100).round(1)
|
|
1108
|
+
return if reduction <= 0
|
|
1109
|
+
|
|
1110
|
+
display_message(" š Token optimization: #{reduction}% reduction " \
|
|
1111
|
+
"(#{format_bytes(stats[:total_input_bytes])} ā #{format_bytes(stats[:total_output_bytes])})", type: :info)
|
|
1112
|
+
rescue
|
|
1113
|
+
# Silently ignore errors in stats display
|
|
1114
|
+
end
|
|
1115
|
+
|
|
1116
|
+
def format_bytes(bytes)
|
|
1117
|
+
if bytes >= 1024 * 1024
|
|
1118
|
+
"#{(bytes / 1024.0 / 1024.0).round(1)}MB"
|
|
1119
|
+
elsif bytes >= 1024
|
|
1120
|
+
"#{(bytes / 1024.0).round(1)}KB"
|
|
1121
|
+
else
|
|
1122
|
+
"#{bytes}B"
|
|
1123
|
+
end
|
|
1124
|
+
end
|
|
1125
|
+
|
|
1126
|
+
def summarize_checks(planned)
|
|
1127
|
+
labels = {
|
|
1128
|
+
tests: "tests",
|
|
1129
|
+
lints: "linters",
|
|
1130
|
+
formatters: "formatters",
|
|
1131
|
+
builds: "builds",
|
|
1132
|
+
docs: "docs"
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
summaries = planned.map do |category, commands|
|
|
1136
|
+
count = Array(commands).size
|
|
1137
|
+
next if count.zero?
|
|
1138
|
+
|
|
1139
|
+
label = labels[category] || category.to_s
|
|
1140
|
+
cmd_names = Array(commands).map do |cmd|
|
|
1141
|
+
cmd.is_a?(Hash) ? cmd[:command] : cmd
|
|
1142
|
+
end
|
|
1143
|
+
|
|
1144
|
+
if cmd_names.size <= 2
|
|
1145
|
+
"#{label} (#{cmd_names.join(", ")})"
|
|
1146
|
+
else
|
|
1147
|
+
"#{label} (#{cmd_names.first(2).join(", ")} +#{cmd_names.size - 2} more)"
|
|
1148
|
+
end
|
|
1149
|
+
end.compact
|
|
1150
|
+
|
|
1151
|
+
summaries.join(" | ")
|
|
1152
|
+
rescue => e
|
|
1153
|
+
Aidp.log_warn("work_loop", "summarize_checks_failed", error: e.message)
|
|
1154
|
+
nil
|
|
1155
|
+
end
|
|
1156
|
+
|
|
1157
|
+
def planned_checks_summary
|
|
1158
|
+
return nil unless @test_runner.respond_to?(:planned_commands)
|
|
1159
|
+
|
|
1160
|
+
summarize_checks(@test_runner.planned_commands)
|
|
1161
|
+
end
|
|
1162
|
+
|
|
1163
|
+
def failure_summary_for_log(all_results)
|
|
1164
|
+
Array(all_results).each_with_object([]) do |(category, results), summary|
|
|
1165
|
+
next if results[:success]
|
|
1166
|
+
|
|
1167
|
+
failures = results[:required_failures] || results[:failures] || []
|
|
1168
|
+
count = failures.size
|
|
1169
|
+
commands = Array(failures).map { |f| f[:command] }.compact
|
|
1170
|
+
|
|
1171
|
+
summary << if commands.any?
|
|
1172
|
+
"#{category}: #{count} (#{commands.first(2).join(", ")})"
|
|
1173
|
+
else
|
|
1174
|
+
"#{category}: #{count}"
|
|
1175
|
+
end
|
|
772
1176
|
end
|
|
1177
|
+
rescue => e
|
|
1178
|
+
Aidp.log_warn("work_loop", "failure_summary_for_log_failed", error: e.message)
|
|
1179
|
+
[]
|
|
773
1180
|
end
|
|
774
1181
|
|
|
1182
|
+
# FIX for issue #391: Added completion_reason and task_completion_reason parameters for better logging
|
|
1183
|
+
def log_iteration_status(status, provider:, model:, prompt_length:, checks: nil, failures: nil, task_status: nil,
|
|
1184
|
+
completion_reason: nil, task_completion_reason: nil)
|
|
1185
|
+
context_labels = iteration_context_labels
|
|
1186
|
+
metadata = {
|
|
1187
|
+
step: @step_name,
|
|
1188
|
+
iteration: @iteration_count,
|
|
1189
|
+
state: STATES[@current_state],
|
|
1190
|
+
tier: @thinking_depth_manager.current_tier,
|
|
1191
|
+
provider: provider,
|
|
1192
|
+
model: model,
|
|
1193
|
+
prompt_length: prompt_length,
|
|
1194
|
+
checks: checks,
|
|
1195
|
+
failures: failures,
|
|
1196
|
+
task_status: task_status,
|
|
1197
|
+
completion_reason: completion_reason,
|
|
1198
|
+
task_completion_reason: task_completion_reason
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
metadata.merge!(iteration_context_metadata)
|
|
1202
|
+
metadata.delete_if { |_, value| value.nil? || (value.respond_to?(:empty?) && value.empty?) }
|
|
1203
|
+
|
|
1204
|
+
message = "Iteration #{@iteration_count} for #{@step_name}: #{status}"
|
|
1205
|
+
message += " | #{context_labels.join(" | ")}" if context_labels.any?
|
|
1206
|
+
|
|
1207
|
+
Aidp.log_info("work_loop_iteration",
|
|
1208
|
+
message,
|
|
1209
|
+
**metadata)
|
|
1210
|
+
rescue => e
|
|
1211
|
+
Aidp.log_warn("work_loop", "failed_to_log_iteration_status", error: e.message)
|
|
1212
|
+
end
|
|
1213
|
+
|
|
1214
|
+
# FIX for issue #391: Enhanced work loop header with upfront task filing requirements
|
|
775
1215
|
def build_work_loop_header(step_name, iteration)
|
|
776
1216
|
parts = []
|
|
777
1217
|
parts << "# Work Loop: #{step_name} (Iteration #{iteration})"
|
|
778
1218
|
parts << ""
|
|
779
1219
|
parts << "## Instructions"
|
|
780
1220
|
parts << "You are working in a work loop. Your responsibilities:"
|
|
781
|
-
parts << "1.
|
|
782
|
-
parts << "2.
|
|
783
|
-
parts << "3.
|
|
784
|
-
parts << "4.
|
|
785
|
-
parts << "5.
|
|
1221
|
+
parts << "1. **FIRST**: File tasks for all work items (see Task Filing section below)"
|
|
1222
|
+
parts << "2. Read the task description below to understand what needs to be done"
|
|
1223
|
+
parts << "3. **Write/edit CODE files** to implement the required changes"
|
|
1224
|
+
parts << "4. Run tests to verify your changes work correctly"
|
|
1225
|
+
parts << "5. Update task status as you complete items"
|
|
1226
|
+
parts << "6. When ALL tasks are complete and tests pass, mark the step COMPLETE"
|
|
786
1227
|
parts << ""
|
|
787
1228
|
parts << "## Important Notes"
|
|
788
1229
|
parts << "- You have full file system access - create and edit files as needed"
|
|
@@ -790,29 +1231,53 @@ module Aidp
|
|
|
790
1231
|
parts << "- After you finish, tests and linters will run automatically"
|
|
791
1232
|
parts << "- If tests/linters fail, you'll see the errors in the next iteration and can fix them"
|
|
792
1233
|
parts << ""
|
|
1234
|
+
parts << "## ā ļø Code Changes Required"
|
|
1235
|
+
parts << "**IMPORTANT**: This implementation requires actual code changes."
|
|
1236
|
+
parts << "- Documentation-only changes will NOT be accepted as complete"
|
|
1237
|
+
parts << "- Configuration-only changes will NOT be accepted as complete"
|
|
1238
|
+
parts << "- You must modify/create code files (.rb, .py, .js, etc.) to implement the feature/fix"
|
|
1239
|
+
parts << "- Tests should accompany code changes"
|
|
1240
|
+
parts << ""
|
|
793
1241
|
|
|
794
1242
|
if @config.task_completion_required?
|
|
795
|
-
parts << "## Task
|
|
796
|
-
parts << "**CRITICAL**: This work loop requires task tracking
|
|
1243
|
+
parts << "## Task Filing (REQUIRED - DO THIS FIRST)"
|
|
1244
|
+
parts << "**CRITICAL**: This work loop requires task tracking. You MUST file tasks before implementation."
|
|
1245
|
+
parts << ""
|
|
1246
|
+
parts << "### Step 1: File Tasks Immediately"
|
|
1247
|
+
parts << "In your FIRST iteration, analyze the requirements and file tasks for ALL work:"
|
|
797
1248
|
parts << ""
|
|
798
|
-
parts << "
|
|
799
|
-
parts << "
|
|
800
|
-
parts << "
|
|
801
|
-
parts << "
|
|
802
|
-
parts << "
|
|
803
|
-
parts << "5. **IMPORTANT**: When you write STATUS: COMPLETE, also mark all your tasks as done!"
|
|
1249
|
+
parts << "```text"
|
|
1250
|
+
parts << "File task: \"Implement [feature/fix description]\" priority: high tags: implementation"
|
|
1251
|
+
parts << "File task: \"Add unit tests for [feature]\" priority: high tags: testing"
|
|
1252
|
+
parts << "File task: \"Add integration tests if needed\" priority: medium tags: testing"
|
|
1253
|
+
parts << "```"
|
|
804
1254
|
parts << ""
|
|
805
|
-
parts << "
|
|
806
|
-
parts << "
|
|
807
|
-
parts << "
|
|
808
|
-
parts << "
|
|
1255
|
+
parts << "### Step 2: Work Through Tasks"
|
|
1256
|
+
parts << "- Pick the highest priority pending task"
|
|
1257
|
+
parts << "- Implement it completely"
|
|
1258
|
+
parts << "- Mark it done: `Update task: task_id status: done`"
|
|
1259
|
+
parts << "- Repeat until all tasks are complete"
|
|
809
1260
|
parts << ""
|
|
810
|
-
parts << "
|
|
1261
|
+
parts << "### Step 3: Complete the Work Loop"
|
|
1262
|
+
parts << "Only after ALL tasks are done:"
|
|
1263
|
+
parts << "- Verify tests pass"
|
|
1264
|
+
parts << "- Add STATUS: COMPLETE to PROMPT.md"
|
|
1265
|
+
parts << ""
|
|
1266
|
+
parts << "### Task Rules"
|
|
1267
|
+
parts << "- **At least ONE task must be filed** - completion blocked without tasks"
|
|
1268
|
+
parts << "- **At least ONE task must be DONE** - completion blocked if all abandoned"
|
|
1269
|
+
parts << "- **Substantive work required** - doc-only changes rejected"
|
|
1270
|
+
parts << ""
|
|
1271
|
+
parts << "**Important**: Tasks exist due to careful planning. Do NOT abandon tasks due to"
|
|
1272
|
+
parts << "perceived complexity - these factors were considered during planning. Only abandon"
|
|
1273
|
+
parts << "when truly obsolete (requirements changed, duplicate, external blockers)."
|
|
1274
|
+
parts << ""
|
|
1275
|
+
parts << "### Task Filing Examples"
|
|
811
1276
|
parts << "- `File task: \"Implement user authentication\" priority: high tags: security,auth`"
|
|
812
1277
|
parts << "- `File task: \"Add tests for login flow\" priority: medium tags: testing`"
|
|
813
1278
|
parts << "- `File task: \"Update documentation\" priority: low tags: docs`"
|
|
814
1279
|
parts << ""
|
|
815
|
-
parts << "Task
|
|
1280
|
+
parts << "### Task Status Update Examples"
|
|
816
1281
|
parts << "- `Update task: task_123_abc status: in_progress`"
|
|
817
1282
|
parts << "- `Update task: task_456_def status: done`"
|
|
818
1283
|
parts << "- `Update task: task_789_ghi status: abandoned reason: \"Requirements changed\"`"
|
|
@@ -832,6 +1297,24 @@ module Aidp
|
|
|
832
1297
|
parts.join("\n")
|
|
833
1298
|
end
|
|
834
1299
|
|
|
1300
|
+
def iteration_context_metadata
|
|
1301
|
+
ctx = (@options || {}).merge(@work_context || {})
|
|
1302
|
+
{
|
|
1303
|
+
issue: issue_context_label(ctx),
|
|
1304
|
+
pr: pr_context_label(ctx),
|
|
1305
|
+
step_position: step_position_label(@step_name, ctx)
|
|
1306
|
+
}.compact
|
|
1307
|
+
end
|
|
1308
|
+
|
|
1309
|
+
def iteration_context_labels
|
|
1310
|
+
meta = iteration_context_metadata
|
|
1311
|
+
labels = []
|
|
1312
|
+
labels << meta[:issue] if meta[:issue]
|
|
1313
|
+
labels << meta[:pr] if meta[:pr]
|
|
1314
|
+
labels << meta[:step_position] if meta[:step_position]
|
|
1315
|
+
labels
|
|
1316
|
+
end
|
|
1317
|
+
|
|
835
1318
|
def prompt_marked_complete?
|
|
836
1319
|
prompt_content = @prompt_manager.read
|
|
837
1320
|
return false unless prompt_content
|
|
@@ -938,30 +1421,50 @@ module Aidp
|
|
|
938
1421
|
|
|
939
1422
|
# Check if we should reinject the style guide at this iteration
|
|
940
1423
|
def should_reinject_style_guide?
|
|
1424
|
+
# Skip reinjection for providers with instruction files (Claude, GitHub Copilot)
|
|
1425
|
+
current_provider = @provider_manager&.current_provider
|
|
1426
|
+
return false unless @style_guide_selector.provider_needs_style_guide?(current_provider)
|
|
1427
|
+
|
|
941
1428
|
# Reinject on intervals (5, 10, 15, etc.) but not on iteration 1
|
|
942
1429
|
@iteration_count > 1 && (@iteration_count % STYLE_GUIDE_REMINDER_INTERVAL == 0)
|
|
943
1430
|
end
|
|
944
1431
|
|
|
945
1432
|
# Create style guide reminder text
|
|
946
1433
|
def reinject_style_guide_reminder
|
|
947
|
-
|
|
1434
|
+
current_provider = @provider_manager&.current_provider
|
|
1435
|
+
|
|
1436
|
+
# Skip for providers with instruction files
|
|
1437
|
+
unless @style_guide_selector.provider_needs_style_guide?(current_provider)
|
|
1438
|
+
Aidp.log_debug("work_loop", "skipping_style_guide_reminder",
|
|
1439
|
+
provider: current_provider,
|
|
1440
|
+
reason: "provider has instruction file")
|
|
1441
|
+
return ""
|
|
1442
|
+
end
|
|
1443
|
+
|
|
948
1444
|
template_content = load_current_template
|
|
949
1445
|
|
|
1446
|
+
# Use provider-aware style guide loading with context-based section selection
|
|
1447
|
+
style_guide = load_style_guide_for_provider(@work_context)
|
|
1448
|
+
|
|
950
1449
|
reminder = []
|
|
951
1450
|
reminder << "### š Style Guide & Template Reminder (Iteration #{@iteration_count})"
|
|
952
1451
|
reminder << ""
|
|
953
1452
|
reminder << "**IMPORTANT**: To prevent drift from project conventions, please review:"
|
|
954
1453
|
reminder << ""
|
|
955
1454
|
|
|
956
|
-
if style_guide
|
|
957
|
-
reminder << "####
|
|
958
|
-
reminder << "```"
|
|
959
|
-
# Include
|
|
960
|
-
style_guide_preview =
|
|
1455
|
+
if style_guide && !style_guide.empty?
|
|
1456
|
+
reminder << "#### Relevant Style Guide Sections"
|
|
1457
|
+
reminder << "```markdown"
|
|
1458
|
+
# Include selected sections (already limited by selector)
|
|
1459
|
+
style_guide_preview = if style_guide.length > 2000
|
|
1460
|
+
style_guide[0...2000] + "\n...(truncated)"
|
|
1461
|
+
else
|
|
1462
|
+
style_guide
|
|
1463
|
+
end
|
|
961
1464
|
reminder << style_guide_preview
|
|
962
1465
|
reminder << "```"
|
|
963
1466
|
reminder << ""
|
|
964
|
-
display_message(" [STYLE_GUIDE] Re-injecting
|
|
1467
|
+
display_message(" [STYLE_GUIDE] Re-injecting selected STYLE_GUIDE sections at iteration #{@iteration_count}", type: :info)
|
|
965
1468
|
end
|
|
966
1469
|
|
|
967
1470
|
if template_content
|
|
@@ -1051,6 +1554,74 @@ module Aidp
|
|
|
1051
1554
|
File.exist?(style_guide_path) ? File.read(style_guide_path) : nil
|
|
1052
1555
|
end
|
|
1053
1556
|
|
|
1557
|
+
# Load style guide content appropriate for the current provider and context
|
|
1558
|
+
# Returns nil for providers with instruction files (Claude, GitHub Copilot)
|
|
1559
|
+
# Returns selected STYLE_GUIDE sections for other providers
|
|
1560
|
+
#
|
|
1561
|
+
# @param context [Hash] Task context for keyword extraction
|
|
1562
|
+
# @return [String, nil] Style guide content or nil if not needed
|
|
1563
|
+
def load_style_guide_for_provider(context = {})
|
|
1564
|
+
current_provider = @provider_manager&.current_provider
|
|
1565
|
+
|
|
1566
|
+
# Skip style guide for providers with their own instruction files
|
|
1567
|
+
unless @style_guide_selector.provider_needs_style_guide?(current_provider)
|
|
1568
|
+
Aidp.log_debug("work_loop", "skipping_style_guide",
|
|
1569
|
+
provider: current_provider,
|
|
1570
|
+
reason: "provider has instruction file")
|
|
1571
|
+
return nil
|
|
1572
|
+
end
|
|
1573
|
+
|
|
1574
|
+
# Extract keywords from context for intelligent section selection
|
|
1575
|
+
keywords = extract_style_guide_keywords(context)
|
|
1576
|
+
|
|
1577
|
+
# Select relevant sections from STYLE_GUIDE.md
|
|
1578
|
+
content = @style_guide_selector.select_sections(
|
|
1579
|
+
keywords: keywords,
|
|
1580
|
+
include_core: true,
|
|
1581
|
+
max_lines: 500 # Limit to keep prompt size manageable
|
|
1582
|
+
)
|
|
1583
|
+
|
|
1584
|
+
return nil if content.nil? || content.empty?
|
|
1585
|
+
|
|
1586
|
+
Aidp.log_debug("work_loop", "style_guide_selected",
|
|
1587
|
+
provider: current_provider,
|
|
1588
|
+
keywords: keywords,
|
|
1589
|
+
content_lines: content.lines.count)
|
|
1590
|
+
|
|
1591
|
+
content
|
|
1592
|
+
end
|
|
1593
|
+
|
|
1594
|
+
# Extract keywords from task context for style guide section selection
|
|
1595
|
+
#
|
|
1596
|
+
# @param context [Hash] Task context
|
|
1597
|
+
# @return [Array<String>] Keywords for section selection
|
|
1598
|
+
def extract_style_guide_keywords(context)
|
|
1599
|
+
keywords = []
|
|
1600
|
+
|
|
1601
|
+
# Extract from step name
|
|
1602
|
+
step_lower = @step_name.to_s.downcase
|
|
1603
|
+
keywords << "testing" if step_lower.include?("test")
|
|
1604
|
+
keywords << "implementation" if step_lower.include?("implement")
|
|
1605
|
+
keywords << "refactor" if step_lower.include?("refactor")
|
|
1606
|
+
|
|
1607
|
+
# Extract from user input
|
|
1608
|
+
user_input = context[:user_input]
|
|
1609
|
+
if user_input.is_a?(Hash)
|
|
1610
|
+
keywords.concat(@style_guide_selector.extract_keywords(user_input.values.join(" ")))
|
|
1611
|
+
elsif user_input.is_a?(String)
|
|
1612
|
+
keywords.concat(@style_guide_selector.extract_keywords(user_input))
|
|
1613
|
+
end
|
|
1614
|
+
|
|
1615
|
+
# Extract from affected files
|
|
1616
|
+
affected_files = context[:affected_files] || []
|
|
1617
|
+
affected_files.each do |file|
|
|
1618
|
+
keywords << "testing" if file.include?("spec") || file.include?("test")
|
|
1619
|
+
keywords << "tty" if file.include?("cli") || file.include?("tui")
|
|
1620
|
+
end
|
|
1621
|
+
|
|
1622
|
+
keywords.uniq
|
|
1623
|
+
end
|
|
1624
|
+
|
|
1054
1625
|
def format_user_input(user_input)
|
|
1055
1626
|
return nil if user_input.nil? || user_input.empty?
|
|
1056
1627
|
|
|
@@ -1145,6 +1716,29 @@ module Aidp
|
|
|
1145
1716
|
display_message("")
|
|
1146
1717
|
end
|
|
1147
1718
|
|
|
1719
|
+
# Display security status for Rule of Two enforcement
|
|
1720
|
+
def display_security_status
|
|
1721
|
+
status = @security_adapter.status
|
|
1722
|
+
return unless status[:enabled]
|
|
1723
|
+
|
|
1724
|
+
display_message("\nš Security (Rule of Two):", type: :info)
|
|
1725
|
+
display_message(" #{status[:status_string]}", type: :info)
|
|
1726
|
+
|
|
1727
|
+
if status[:state]
|
|
1728
|
+
state = status[:state]
|
|
1729
|
+
flags = []
|
|
1730
|
+
flags << "untrusted_input (#{state[:untrusted_input_source]})" if state[:untrusted_input]
|
|
1731
|
+
flags << "private_data (#{state[:private_data_source]})" if state[:private_data]
|
|
1732
|
+
flags << "egress (#{state[:egress_source]})" if state[:egress]
|
|
1733
|
+
|
|
1734
|
+
if flags.any?
|
|
1735
|
+
display_message(" Active flags: #{flags.join(", ")}", type: :info)
|
|
1736
|
+
end
|
|
1737
|
+
end
|
|
1738
|
+
|
|
1739
|
+
display_message("")
|
|
1740
|
+
end
|
|
1741
|
+
|
|
1148
1742
|
# Display pending tasks from persistent tasklist
|
|
1149
1743
|
def display_pending_tasks
|
|
1150
1744
|
pending_tasks = @persistent_tasklist.pending
|
|
@@ -1221,46 +1815,113 @@ module Aidp
|
|
|
1221
1815
|
end
|
|
1222
1816
|
|
|
1223
1817
|
# Check if tasks are required and all are completed or abandoned
|
|
1224
|
-
# Returns {complete: boolean, message: string}
|
|
1818
|
+
# Returns {complete: boolean, message: string, reason: string}
|
|
1225
1819
|
# Note: Tasks are project-scoped, not session-scoped. This allows tasks created
|
|
1226
1820
|
# in planning phases to be completed in build phases.
|
|
1821
|
+
#
|
|
1822
|
+
# FIX for issue #391: Prevent premature completion when tasks haven't been created
|
|
1823
|
+
# The previous logic allowed completion with empty task list, which enabled
|
|
1824
|
+
# the work loop to complete before actually implementing anything.
|
|
1227
1825
|
def check_task_completion
|
|
1228
|
-
|
|
1826
|
+
Aidp.log_debug("work_loop", "check_task_completion_start",
|
|
1827
|
+
task_completion_required: @config.task_completion_required?,
|
|
1828
|
+
iteration: @iteration_count)
|
|
1829
|
+
|
|
1830
|
+
unless @config.task_completion_required?
|
|
1831
|
+
Aidp.log_debug("work_loop", "check_task_completion_skipped",
|
|
1832
|
+
reason: "task_completion_not_required")
|
|
1833
|
+
return {complete: true, message: nil, reason: "task_completion_not_required"}
|
|
1834
|
+
end
|
|
1229
1835
|
|
|
1230
1836
|
all_tasks = @persistent_tasklist.all
|
|
1231
1837
|
|
|
1232
|
-
|
|
1233
|
-
|
|
1838
|
+
Aidp.log_debug("work_loop", "check_task_completion_task_count",
|
|
1839
|
+
total_tasks: all_tasks.size,
|
|
1840
|
+
task_ids: all_tasks.map(&:id))
|
|
1841
|
+
|
|
1842
|
+
# FIX for issue #391: Require at least one task when task_completion is enabled
|
|
1843
|
+
# Empty task list now blocks completion to prevent premature PR creation
|
|
1844
|
+
# This ensures the agent has actually created and completed work items
|
|
1234
1845
|
if all_tasks.empty?
|
|
1235
|
-
|
|
1846
|
+
Aidp.log_debug("work_loop", "check_task_completion_empty_tasks",
|
|
1847
|
+
reason: "no_tasks_filed",
|
|
1848
|
+
iteration: @iteration_count)
|
|
1849
|
+
|
|
1850
|
+
# After multiple iterations, require tasks - agent should have filed some by now
|
|
1851
|
+
if @iteration_count >= 3
|
|
1852
|
+
return {
|
|
1853
|
+
complete: false,
|
|
1854
|
+
message: "No tasks have been filed yet. You must create at least one task using:\n" \
|
|
1855
|
+
" File task: \"description\" priority: high|medium|low tags: tag1,tag2\n\n" \
|
|
1856
|
+
"Tasks help track progress and ensure complete implementation.",
|
|
1857
|
+
reason: "no_tasks_after_iterations"
|
|
1858
|
+
}
|
|
1859
|
+
end
|
|
1860
|
+
|
|
1861
|
+
# In early iterations, allow progress but don't allow completion
|
|
1862
|
+
return {
|
|
1863
|
+
complete: false,
|
|
1864
|
+
message: "Please file tasks to track your implementation work.",
|
|
1865
|
+
reason: "no_tasks_early_iteration"
|
|
1866
|
+
}
|
|
1236
1867
|
end
|
|
1237
1868
|
|
|
1238
1869
|
# Count tasks by status
|
|
1239
1870
|
pending_tasks = all_tasks.select { |t| t.status == :pending }
|
|
1240
1871
|
in_progress_tasks = all_tasks.select { |t| t.status == :in_progress }
|
|
1241
1872
|
abandoned_tasks = all_tasks.select { |t| t.status == :abandoned }
|
|
1242
|
-
all_tasks.select { |t| t.status == :done }
|
|
1873
|
+
done_tasks = all_tasks.select { |t| t.status == :done }
|
|
1874
|
+
|
|
1875
|
+
Aidp.log_debug("work_loop", "check_task_completion_status_counts",
|
|
1876
|
+
pending: pending_tasks.size,
|
|
1877
|
+
in_progress: in_progress_tasks.size,
|
|
1878
|
+
abandoned: abandoned_tasks.size,
|
|
1879
|
+
done: done_tasks.size)
|
|
1243
1880
|
|
|
1244
1881
|
# If tasks exist, all must be done or abandoned before completion
|
|
1245
1882
|
incomplete_tasks = pending_tasks + in_progress_tasks
|
|
1246
1883
|
|
|
1247
1884
|
if incomplete_tasks.any?
|
|
1248
1885
|
task_list = incomplete_tasks.map { |t| "- #{t.description} (#{t.status}, session: #{t.session})" }.join("\n")
|
|
1886
|
+
Aidp.log_debug("work_loop", "check_task_completion_incomplete",
|
|
1887
|
+
incomplete_count: incomplete_tasks.size,
|
|
1888
|
+
incomplete_ids: incomplete_tasks.map(&:id))
|
|
1249
1889
|
return {
|
|
1250
1890
|
complete: false,
|
|
1251
|
-
message: "Tasks remain incomplete:\n#{task_list}\n\nComplete all tasks or abandon them with reason before marking work complete."
|
|
1891
|
+
message: "Tasks remain incomplete:\n#{task_list}\n\nComplete all tasks or abandon them with reason before marking work complete.",
|
|
1892
|
+
reason: "incomplete_tasks"
|
|
1893
|
+
}
|
|
1894
|
+
end
|
|
1895
|
+
|
|
1896
|
+
# FIX for issue #391: Require at least one done task, not just abandoned
|
|
1897
|
+
# This prevents scenarios where all tasks are abandoned without any work
|
|
1898
|
+
if done_tasks.empty? && abandoned_tasks.any?
|
|
1899
|
+
Aidp.log_debug("work_loop", "check_task_completion_all_abandoned",
|
|
1900
|
+
abandoned_count: abandoned_tasks.size)
|
|
1901
|
+
return {
|
|
1902
|
+
complete: false,
|
|
1903
|
+
message: "All tasks have been abandoned with no completed work. " \
|
|
1904
|
+
"At least one task must be completed, or explain why no implementation is needed.",
|
|
1905
|
+
reason: "all_tasks_abandoned"
|
|
1252
1906
|
}
|
|
1253
1907
|
end
|
|
1254
1908
|
|
|
1255
1909
|
# If there are abandoned tasks, confirm with user
|
|
1256
1910
|
if abandoned_tasks.any? && !all_abandoned_tasks_confirmed?(abandoned_tasks)
|
|
1911
|
+
Aidp.log_debug("work_loop", "check_task_completion_unconfirmed_abandoned",
|
|
1912
|
+
abandoned_count: abandoned_tasks.size)
|
|
1257
1913
|
return {
|
|
1258
1914
|
complete: false,
|
|
1259
|
-
message: "Abandoned tasks require user confirmation. Please confirm abandoned tasks."
|
|
1915
|
+
message: "Abandoned tasks require user confirmation. Please confirm abandoned tasks.",
|
|
1916
|
+
reason: "unconfirmed_abandoned_tasks"
|
|
1260
1917
|
}
|
|
1261
1918
|
end
|
|
1262
1919
|
|
|
1263
|
-
|
|
1920
|
+
Aidp.log_debug("work_loop", "check_task_completion_success",
|
|
1921
|
+
done_count: done_tasks.size,
|
|
1922
|
+
abandoned_count: abandoned_tasks.size)
|
|
1923
|
+
|
|
1924
|
+
{complete: true, message: nil, reason: "all_tasks_complete"}
|
|
1264
1925
|
end
|
|
1265
1926
|
|
|
1266
1927
|
# Check if all abandoned tasks have been confirmed
|