aidp 0.26.0 → 0.28.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 +89 -0
- data/lib/aidp/cli/checkpoint_command.rb +198 -0
- data/lib/aidp/cli/config_command.rb +71 -0
- data/lib/aidp/cli/enhanced_input.rb +2 -0
- data/lib/aidp/cli/first_run_wizard.rb +8 -7
- data/lib/aidp/cli/harness_command.rb +102 -0
- data/lib/aidp/cli/jobs_command.rb +3 -3
- data/lib/aidp/cli/mcp_dashboard.rb +4 -3
- data/lib/aidp/cli/models_command.rb +661 -0
- data/lib/aidp/cli/providers_command.rb +223 -0
- data/lib/aidp/cli.rb +45 -464
- data/lib/aidp/config.rb +54 -0
- data/lib/aidp/daemon/runner.rb +2 -2
- data/lib/aidp/debug_mixin.rb +25 -10
- data/lib/aidp/execute/agent_signal_parser.rb +22 -0
- data/lib/aidp/execute/async_work_loop_runner.rb +2 -1
- data/lib/aidp/execute/checkpoint_display.rb +38 -37
- data/lib/aidp/execute/interactive_repl.rb +2 -1
- data/lib/aidp/execute/prompt_manager.rb +4 -4
- data/lib/aidp/execute/repl_macros.rb +2 -2
- data/lib/aidp/execute/steps.rb +94 -1
- data/lib/aidp/execute/work_loop_runner.rb +238 -19
- data/lib/aidp/execute/workflow_selector.rb +4 -27
- data/lib/aidp/firewall/provider_requirements_collector.rb +262 -0
- data/lib/aidp/harness/ai_decision_engine.rb +35 -2
- data/lib/aidp/harness/config_manager.rb +5 -10
- data/lib/aidp/harness/config_schema.rb +8 -0
- data/lib/aidp/harness/configuration.rb +40 -2
- data/lib/aidp/harness/enhanced_runner.rb +25 -19
- data/lib/aidp/harness/error_handler.rb +23 -73
- data/lib/aidp/harness/model_cache.rb +269 -0
- data/lib/aidp/harness/model_discovery_service.rb +259 -0
- data/lib/aidp/harness/model_registry.rb +201 -0
- data/lib/aidp/harness/provider_factory.rb +11 -2
- data/lib/aidp/harness/runner.rb +5 -0
- data/lib/aidp/harness/state_manager.rb +0 -7
- data/lib/aidp/harness/thinking_depth_manager.rb +202 -7
- data/lib/aidp/harness/ui/enhanced_tui.rb +8 -18
- data/lib/aidp/harness/ui/enhanced_workflow_selector.rb +0 -18
- data/lib/aidp/harness/ui/progress_display.rb +6 -2
- data/lib/aidp/harness/user_interface.rb +0 -58
- data/lib/aidp/init/runner.rb +7 -2
- data/lib/aidp/message_display.rb +0 -46
- data/lib/aidp/planning/analyzers/feedback_analyzer.rb +365 -0
- data/lib/aidp/planning/builders/agile_plan_builder.rb +387 -0
- data/lib/aidp/planning/builders/project_plan_builder.rb +193 -0
- data/lib/aidp/planning/generators/gantt_generator.rb +190 -0
- data/lib/aidp/planning/generators/iteration_plan_generator.rb +392 -0
- data/lib/aidp/planning/generators/legacy_research_planner.rb +473 -0
- data/lib/aidp/planning/generators/marketing_report_generator.rb +348 -0
- data/lib/aidp/planning/generators/mvp_scope_generator.rb +310 -0
- data/lib/aidp/planning/generators/user_test_plan_generator.rb +373 -0
- data/lib/aidp/planning/generators/wbs_generator.rb +259 -0
- data/lib/aidp/planning/mappers/persona_mapper.rb +163 -0
- data/lib/aidp/planning/parsers/document_parser.rb +141 -0
- data/lib/aidp/planning/parsers/feedback_data_parser.rb +252 -0
- data/lib/aidp/provider_manager.rb +8 -32
- data/lib/aidp/providers/adapter.rb +2 -4
- data/lib/aidp/providers/aider.rb +264 -0
- data/lib/aidp/providers/anthropic.rb +206 -121
- data/lib/aidp/providers/base.rb +123 -3
- data/lib/aidp/providers/capability_registry.rb +0 -1
- data/lib/aidp/providers/codex.rb +75 -70
- data/lib/aidp/providers/cursor.rb +87 -59
- data/lib/aidp/providers/gemini.rb +57 -60
- data/lib/aidp/providers/github_copilot.rb +19 -66
- data/lib/aidp/providers/kilocode.rb +35 -80
- data/lib/aidp/providers/opencode.rb +35 -80
- data/lib/aidp/setup/wizard.rb +555 -8
- data/lib/aidp/version.rb +1 -1
- data/lib/aidp/watch/build_processor.rb +211 -30
- data/lib/aidp/watch/change_request_processor.rb +128 -14
- data/lib/aidp/watch/ci_fix_processor.rb +103 -37
- data/lib/aidp/watch/ci_log_extractor.rb +258 -0
- data/lib/aidp/watch/github_state_extractor.rb +177 -0
- data/lib/aidp/watch/implementation_verifier.rb +284 -0
- data/lib/aidp/watch/plan_generator.rb +95 -52
- data/lib/aidp/watch/plan_processor.rb +7 -6
- data/lib/aidp/watch/repository_client.rb +245 -17
- data/lib/aidp/watch/review_processor.rb +100 -19
- data/lib/aidp/watch/reviewers/base_reviewer.rb +1 -1
- data/lib/aidp/watch/runner.rb +181 -29
- data/lib/aidp/watch/state_store.rb +22 -1
- data/lib/aidp/workflows/definitions.rb +147 -0
- data/lib/aidp/workflows/guided_agent.rb +3 -3
- data/lib/aidp/workstream_cleanup.rb +245 -0
- data/lib/aidp/worktree.rb +19 -0
- data/templates/aidp-development.yml.example +2 -2
- data/templates/aidp-production.yml.example +3 -3
- data/templates/aidp.yml.example +57 -0
- data/templates/implementation/generate_tdd_specs.md +213 -0
- data/templates/implementation/iterative_implementation.md +122 -0
- data/templates/planning/agile/analyze_feedback.md +183 -0
- data/templates/planning/agile/generate_iteration_plan.md +179 -0
- data/templates/planning/agile/generate_legacy_research_plan.md +171 -0
- data/templates/planning/agile/generate_marketing_report.md +162 -0
- data/templates/planning/agile/generate_mvp_scope.md +127 -0
- data/templates/planning/agile/generate_user_test_plan.md +143 -0
- data/templates/planning/agile/ingest_feedback.md +174 -0
- data/templates/planning/assemble_project_plan.md +113 -0
- data/templates/planning/assign_personas.md +108 -0
- data/templates/planning/create_tasks.md +52 -6
- data/templates/planning/generate_gantt.md +86 -0
- data/templates/planning/generate_wbs.md +85 -0
- data/templates/planning/initialize_planning_mode.md +70 -0
- data/templates/skills/README.md +2 -2
- data/templates/skills/marketing_strategist/SKILL.md +279 -0
- data/templates/skills/product_manager/SKILL.md +177 -0
- data/templates/skills/ruby_aidp_planning/SKILL.md +497 -0
- data/templates/skills/ruby_rspec_tdd/SKILL.md +514 -0
- data/templates/skills/ux_researcher/SKILL.md +222 -0
- metadata +47 -1
|
@@ -8,6 +8,7 @@ require_relative "work_loop_unit_scheduler"
|
|
|
8
8
|
require_relative "deterministic_unit"
|
|
9
9
|
require_relative "agent_signal_parser"
|
|
10
10
|
require_relative "../harness/test_runner"
|
|
11
|
+
require_relative "../errors"
|
|
11
12
|
|
|
12
13
|
module Aidp
|
|
13
14
|
module Execute
|
|
@@ -46,10 +47,11 @@ module Aidp
|
|
|
46
47
|
@project_dir = project_dir
|
|
47
48
|
@provider_manager = provider_manager
|
|
48
49
|
@config = config
|
|
50
|
+
@prompt = options[:prompt] || TTY::Prompt.new
|
|
49
51
|
@prompt_manager = PromptManager.new(project_dir, config: config)
|
|
50
52
|
@test_runner = Aidp::Harness::TestRunner.new(project_dir, config)
|
|
51
53
|
@checkpoint = Checkpoint.new(project_dir)
|
|
52
|
-
@checkpoint_display = CheckpointDisplay.new
|
|
54
|
+
@checkpoint_display = CheckpointDisplay.new(prompt: @prompt)
|
|
53
55
|
@guard_policy = GuardPolicy.new(project_dir, config.guards_config)
|
|
54
56
|
@persistent_tasklist = PersistentTasklist.new(project_dir)
|
|
55
57
|
@iteration_count = 0
|
|
@@ -62,7 +64,7 @@ module Aidp
|
|
|
62
64
|
|
|
63
65
|
# Initialize thinking depth manager for intelligent model selection
|
|
64
66
|
require_relative "../harness/thinking_depth_manager"
|
|
65
|
-
@thinking_depth_manager = Aidp::Harness::ThinkingDepthManager.new(config)
|
|
67
|
+
@thinking_depth_manager = options[:thinking_depth_manager] || Aidp::Harness::ThinkingDepthManager.new(config)
|
|
66
68
|
@consecutive_failures = 0
|
|
67
69
|
@last_tier = nil
|
|
68
70
|
end
|
|
@@ -160,6 +162,10 @@ module Aidp
|
|
|
160
162
|
# Wrap agent call in exception handling for true fix-forward
|
|
161
163
|
begin
|
|
162
164
|
agent_result = apply_patch
|
|
165
|
+
rescue Aidp::Errors::ConfigurationError
|
|
166
|
+
# Configuration errors should crash immediately (crash-early principle)
|
|
167
|
+
# Re-raise without catching
|
|
168
|
+
raise
|
|
163
169
|
rescue => e
|
|
164
170
|
# Convert exception to error result for fix-forward handling
|
|
165
171
|
Aidp.logger.error("work_loop", "Exception during agent call",
|
|
@@ -178,6 +184,27 @@ module Aidp
|
|
|
178
184
|
next
|
|
179
185
|
end
|
|
180
186
|
|
|
187
|
+
# Check for fatal configuration errors (crash early per LLM_STYLE_GUIDE)
|
|
188
|
+
if agent_result[:status] == "error" && agent_result[:message]&.include?("No model available")
|
|
189
|
+
tier = @thinking_depth_manager.current_tier
|
|
190
|
+
provider = @provider_manager.current_provider
|
|
191
|
+
|
|
192
|
+
error_msg = "No model configured for thinking tier '#{tier}'.\n\n" \
|
|
193
|
+
"Current provider: #{provider}\n" \
|
|
194
|
+
"Required tier: #{tier}\n\n" \
|
|
195
|
+
"To fix this, add a model to your aidp.yml:\n\n" \
|
|
196
|
+
"thinking_depth:\n" \
|
|
197
|
+
" tiers:\n" \
|
|
198
|
+
" #{tier}:\n" \
|
|
199
|
+
" models:\n" \
|
|
200
|
+
" - provider: #{provider}\n" \
|
|
201
|
+
" model: <model-name> # e.g., claude-3-5-sonnet-20241022\n\n" \
|
|
202
|
+
"Or run: aidp models list\n" \
|
|
203
|
+
"to see available models for your configured providers."
|
|
204
|
+
|
|
205
|
+
raise Aidp::Errors::ConfigurationError, error_msg
|
|
206
|
+
end
|
|
207
|
+
|
|
181
208
|
# Process agent output for task filing signals
|
|
182
209
|
process_task_filing(agent_result)
|
|
183
210
|
|
|
@@ -215,23 +242,39 @@ module Aidp
|
|
|
215
242
|
build_results[:success] &&
|
|
216
243
|
doc_results[:success]
|
|
217
244
|
|
|
245
|
+
# Check task completion status
|
|
246
|
+
task_completion_result = check_task_completion
|
|
247
|
+
|
|
218
248
|
if all_checks_pass
|
|
219
249
|
transition_to(:pass)
|
|
220
250
|
|
|
221
251
|
if agent_marked_complete?(agent_result)
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
252
|
+
# Check if tasks are complete
|
|
253
|
+
if task_completion_result[:complete]
|
|
254
|
+
transition_to(:done)
|
|
255
|
+
record_final_checkpoint(all_results)
|
|
256
|
+
display_task_summary
|
|
257
|
+
display_message("✅ Step #{@step_name} completed after #{@iteration_count} iterations", type: :success)
|
|
258
|
+
display_state_summary
|
|
259
|
+
archive_and_cleanup
|
|
260
|
+
|
|
261
|
+
return build_agentic_payload(
|
|
262
|
+
agent_result: agent_result,
|
|
263
|
+
response: build_success_result(agent_result),
|
|
264
|
+
summary: agent_result[:output],
|
|
265
|
+
completed: true,
|
|
266
|
+
terminate: true
|
|
267
|
+
)
|
|
268
|
+
else
|
|
269
|
+
# All checks passed but tasks not complete
|
|
270
|
+
display_message(" All checks passed but tasks not complete", type: :warning)
|
|
271
|
+
display_message(" #{task_completion_result[:message]}", type: :warning)
|
|
272
|
+
display_task_summary
|
|
273
|
+
transition_to(:next_patch)
|
|
274
|
+
|
|
275
|
+
# Append task completion requirement to PROMPT.md
|
|
276
|
+
append_task_requirement_to_prompt(task_completion_result[:message])
|
|
277
|
+
end
|
|
235
278
|
else
|
|
236
279
|
display_message(" All checks passed but work not marked complete", type: :info)
|
|
237
280
|
transition_to(:next_patch)
|
|
@@ -742,10 +785,43 @@ module Aidp
|
|
|
742
785
|
parts << "- After you finish, tests and linters will run automatically"
|
|
743
786
|
parts << "- If tests/linters fail, you'll see the errors in the next iteration and can fix them"
|
|
744
787
|
parts << ""
|
|
788
|
+
|
|
789
|
+
if @config.task_completion_required?
|
|
790
|
+
parts << "## Task Tracking (REQUIRED)"
|
|
791
|
+
parts << "**CRITICAL**: This work loop requires task tracking for completion."
|
|
792
|
+
parts << ""
|
|
793
|
+
parts << "You must:"
|
|
794
|
+
parts << "1. Create at least one task for this session using: `File task: \"description\"`"
|
|
795
|
+
parts << "2. Track all work items as tasks"
|
|
796
|
+
parts << "3. Update task status as you progress"
|
|
797
|
+
parts << "4. All tasks must be DONE or ABANDONED (with reason) before completion"
|
|
798
|
+
parts << "5. **IMPORTANT**: When you write STATUS: COMPLETE, also mark all your tasks as done!"
|
|
799
|
+
parts << ""
|
|
800
|
+
parts << "**Important**: Tasks in the list exist due to careful planning and requirements analysis."
|
|
801
|
+
parts << "Do NOT abandon tasks due to perceived complexity or scope concerns - these factors were"
|
|
802
|
+
parts << "considered during planning. Only abandon tasks when truly obsolete (requirements changed,"
|
|
803
|
+
parts << "duplicate work, external blockers). When in doubt, mark in_progress and implement."
|
|
804
|
+
parts << ""
|
|
805
|
+
parts << "Task filing examples:"
|
|
806
|
+
parts << "- `File task: \"Implement user authentication\" priority: high tags: security,auth`"
|
|
807
|
+
parts << "- `File task: \"Add tests for login flow\" priority: medium tags: testing`"
|
|
808
|
+
parts << "- `File task: \"Update documentation\" priority: low tags: docs`"
|
|
809
|
+
parts << ""
|
|
810
|
+
parts << "Task status update examples:"
|
|
811
|
+
parts << "- `Update task: task_123_abc status: in_progress`"
|
|
812
|
+
parts << "- `Update task: task_456_def status: done`"
|
|
813
|
+
parts << "- `Update task: task_789_ghi status: abandoned reason: \"Requirements changed\"`"
|
|
814
|
+
parts << ""
|
|
815
|
+
end
|
|
816
|
+
|
|
745
817
|
parts << "## Completion Criteria"
|
|
746
|
-
parts << "Mark this step COMPLETE by adding
|
|
818
|
+
parts << "Mark this step COMPLETE by adding these lines to PROMPT.md:"
|
|
747
819
|
parts << "```"
|
|
748
820
|
parts << "STATUS: COMPLETE"
|
|
821
|
+
if @config.task_completion_required?
|
|
822
|
+
parts << ""
|
|
823
|
+
parts << "Update task: task_xxx_yyy status: done # Mark ALL your tasks as done"
|
|
824
|
+
end
|
|
749
825
|
parts << "```"
|
|
750
826
|
parts << ""
|
|
751
827
|
parts.join("\n")
|
|
@@ -1092,13 +1168,12 @@ module Aidp
|
|
|
1092
1168
|
display_message("")
|
|
1093
1169
|
end
|
|
1094
1170
|
|
|
1095
|
-
# Process agent output for task filing signals
|
|
1171
|
+
# Process agent output for task filing signals and task status updates
|
|
1096
1172
|
def process_task_filing(agent_result)
|
|
1097
1173
|
return unless agent_result && agent_result[:output]
|
|
1098
1174
|
|
|
1175
|
+
# Process new task filings
|
|
1099
1176
|
filed_tasks = AgentSignalParser.parse_task_filing(agent_result[:output])
|
|
1100
|
-
return if filed_tasks.empty?
|
|
1101
|
-
|
|
1102
1177
|
filed_tasks.each do |task_data|
|
|
1103
1178
|
task = @persistent_tasklist.create(
|
|
1104
1179
|
task_data[:description],
|
|
@@ -1111,6 +1186,150 @@ module Aidp
|
|
|
1111
1186
|
Aidp.log_info("tasklist", "Filed new task from agent", task_id: task.id, description: task.description)
|
|
1112
1187
|
display_message("📋 Filed task: #{task.description} (#{task.id})", type: :info)
|
|
1113
1188
|
end
|
|
1189
|
+
|
|
1190
|
+
# Process task status updates
|
|
1191
|
+
status_updates = AgentSignalParser.parse_task_status_updates(agent_result[:output])
|
|
1192
|
+
status_updates.each do |update_data|
|
|
1193
|
+
task = @persistent_tasklist.update_status(
|
|
1194
|
+
update_data[:task_id],
|
|
1195
|
+
update_data[:status],
|
|
1196
|
+
reason: update_data[:reason]
|
|
1197
|
+
)
|
|
1198
|
+
|
|
1199
|
+
status_icon = case update_data[:status]
|
|
1200
|
+
when :done then "✅"
|
|
1201
|
+
when :abandoned then "❌"
|
|
1202
|
+
when :in_progress then "🚧"
|
|
1203
|
+
when :pending then "⏳"
|
|
1204
|
+
else "📋"
|
|
1205
|
+
end
|
|
1206
|
+
|
|
1207
|
+
Aidp.log_info("tasklist", "Updated task status from agent",
|
|
1208
|
+
task_id: task.id,
|
|
1209
|
+
old_status: task.status,
|
|
1210
|
+
new_status: update_data[:status])
|
|
1211
|
+
display_message("#{status_icon} Updated task #{task.id}: #{update_data[:status]}", type: :info)
|
|
1212
|
+
rescue PersistentTasklist::TaskNotFoundError
|
|
1213
|
+
Aidp.log_warn("tasklist", "Task not found for status update", task_id: update_data[:task_id])
|
|
1214
|
+
display_message("⚠️ Task not found: #{update_data[:task_id]}", type: :warning)
|
|
1215
|
+
end
|
|
1216
|
+
end
|
|
1217
|
+
|
|
1218
|
+
# Check if tasks are required and all are completed or abandoned
|
|
1219
|
+
# Returns {complete: boolean, message: string}
|
|
1220
|
+
# Note: Tasks are project-scoped, not session-scoped. This allows tasks created
|
|
1221
|
+
# in planning phases to be completed in build phases.
|
|
1222
|
+
def check_task_completion
|
|
1223
|
+
return {complete: true, message: nil} unless @config.task_completion_required?
|
|
1224
|
+
|
|
1225
|
+
all_tasks = @persistent_tasklist.all
|
|
1226
|
+
|
|
1227
|
+
# If no tasks exist yet, allow completion - agent can work without tasks initially
|
|
1228
|
+
# This supports workflows where no planning phase created tasks
|
|
1229
|
+
if all_tasks.empty?
|
|
1230
|
+
return {complete: true, message: nil}
|
|
1231
|
+
end
|
|
1232
|
+
|
|
1233
|
+
# Count tasks by status
|
|
1234
|
+
pending_tasks = all_tasks.select { |t| t.status == :pending }
|
|
1235
|
+
in_progress_tasks = all_tasks.select { |t| t.status == :in_progress }
|
|
1236
|
+
abandoned_tasks = all_tasks.select { |t| t.status == :abandoned }
|
|
1237
|
+
all_tasks.select { |t| t.status == :done }
|
|
1238
|
+
|
|
1239
|
+
# If tasks exist, all must be done or abandoned before completion
|
|
1240
|
+
incomplete_tasks = pending_tasks + in_progress_tasks
|
|
1241
|
+
|
|
1242
|
+
if incomplete_tasks.any?
|
|
1243
|
+
task_list = incomplete_tasks.map { |t| "- #{t.description} (#{t.status}, session: #{t.session})" }.join("\n")
|
|
1244
|
+
return {
|
|
1245
|
+
complete: false,
|
|
1246
|
+
message: "Tasks remain incomplete:\n#{task_list}\n\nComplete all tasks or abandon them with reason before marking work complete."
|
|
1247
|
+
}
|
|
1248
|
+
end
|
|
1249
|
+
|
|
1250
|
+
# If there are abandoned tasks, confirm with user
|
|
1251
|
+
if abandoned_tasks.any? && !all_abandoned_tasks_confirmed?(abandoned_tasks)
|
|
1252
|
+
return {
|
|
1253
|
+
complete: false,
|
|
1254
|
+
message: "Abandoned tasks require user confirmation. Please confirm abandoned tasks."
|
|
1255
|
+
}
|
|
1256
|
+
end
|
|
1257
|
+
|
|
1258
|
+
{complete: true, message: nil}
|
|
1259
|
+
end
|
|
1260
|
+
|
|
1261
|
+
# Check if all abandoned tasks have been confirmed
|
|
1262
|
+
def all_abandoned_tasks_confirmed?(abandoned_tasks)
|
|
1263
|
+
# For now, we'll consider all abandoned tasks as confirmed if they have a reason
|
|
1264
|
+
# In a future enhancement, this could prompt the user for confirmation
|
|
1265
|
+
abandoned_tasks.all? { |t| t.abandoned_reason && !t.abandoned_reason.strip.empty? }
|
|
1266
|
+
end
|
|
1267
|
+
|
|
1268
|
+
# Display task completion summary
|
|
1269
|
+
def display_task_summary
|
|
1270
|
+
return unless @config.task_completion_required?
|
|
1271
|
+
|
|
1272
|
+
all_tasks = @persistent_tasklist.all
|
|
1273
|
+
return if all_tasks.empty?
|
|
1274
|
+
|
|
1275
|
+
counts = all_tasks.group_by(&:status).transform_values(&:count)
|
|
1276
|
+
|
|
1277
|
+
display_message("\n📋 Task Summary (Project-wide):", type: :info)
|
|
1278
|
+
display_message(" Total: #{all_tasks.size}", type: :info)
|
|
1279
|
+
display_message(" ✅ Done: #{counts[:done] || 0}", type: :success) if counts[:done]
|
|
1280
|
+
display_message(" 🚧 In Progress: #{counts[:in_progress] || 0}", type: :warning) if counts[:in_progress]
|
|
1281
|
+
display_message(" ⏳ Pending: #{counts[:pending] || 0}", type: :warning) if counts[:pending]
|
|
1282
|
+
display_message(" ❌ Abandoned: #{counts[:abandoned] || 0}", type: :error) if counts[:abandoned]
|
|
1283
|
+
display_message("")
|
|
1284
|
+
end
|
|
1285
|
+
|
|
1286
|
+
# Append task completion requirement to PROMPT.md
|
|
1287
|
+
def append_task_requirement_to_prompt(message)
|
|
1288
|
+
task_requirement = []
|
|
1289
|
+
|
|
1290
|
+
task_requirement << "## Task Completion Requirement"
|
|
1291
|
+
task_requirement << ""
|
|
1292
|
+
task_requirement << "**CRITICAL**: #{message}"
|
|
1293
|
+
task_requirement << ""
|
|
1294
|
+
task_requirement << "### How to Complete Tasks"
|
|
1295
|
+
task_requirement << ""
|
|
1296
|
+
task_requirement << "Update task status using these signals in your output:"
|
|
1297
|
+
task_requirement << ""
|
|
1298
|
+
task_requirement << "**Creating tasks:**"
|
|
1299
|
+
task_requirement << "```"
|
|
1300
|
+
task_requirement << 'File task: "Implement feature X" priority: high tags: feature,backend'
|
|
1301
|
+
task_requirement << 'File task: "Add tests for feature X" priority: medium tags: testing'
|
|
1302
|
+
task_requirement << "```"
|
|
1303
|
+
task_requirement << ""
|
|
1304
|
+
task_requirement << "**Updating task status:**"
|
|
1305
|
+
task_requirement << "```"
|
|
1306
|
+
task_requirement << "Update task: task_123_abc status: in_progress"
|
|
1307
|
+
task_requirement << "Update task: task_123_abc status: done"
|
|
1308
|
+
task_requirement << 'Update task: task_456_def status: abandoned reason: "Requirements changed"'
|
|
1309
|
+
task_requirement << "```"
|
|
1310
|
+
task_requirement << ""
|
|
1311
|
+
task_requirement << "**Task states:**"
|
|
1312
|
+
task_requirement << "- ⏳ **pending** - Not started yet"
|
|
1313
|
+
task_requirement << "- 🚧 **in_progress** - Currently working on it"
|
|
1314
|
+
task_requirement << "- ✅ **done** - Completed successfully"
|
|
1315
|
+
task_requirement << "- ❌ **abandoned** - Not doing this (requires reason)"
|
|
1316
|
+
task_requirement << ""
|
|
1317
|
+
task_requirement << "**Completion requirement:**"
|
|
1318
|
+
task_requirement << "All tasks for this session must be marked as DONE or ABANDONED (with reason) before the work loop can complete."
|
|
1319
|
+
task_requirement << ""
|
|
1320
|
+
task_requirement << "**Action Required**: Review the current task list and update status for all tasks."
|
|
1321
|
+
task_requirement << ""
|
|
1322
|
+
|
|
1323
|
+
# Append to PROMPT.md - ensure directory exists
|
|
1324
|
+
begin
|
|
1325
|
+
current_prompt = @prompt_manager.read || ""
|
|
1326
|
+
updated_prompt = current_prompt + "\n\n---\n\n" + task_requirement.join("\n")
|
|
1327
|
+
@prompt_manager.write(updated_prompt, step_name: @step_name)
|
|
1328
|
+
display_message(" [TASK_REQ] Added task completion requirement to PROMPT.md", type: :warning)
|
|
1329
|
+
rescue => e
|
|
1330
|
+
Aidp.log_warn("work_loop", "Failed to append task requirement to PROMPT.md", error: e.message)
|
|
1331
|
+
display_message(" [TASK_REQ] Warning: Could not update PROMPT.md: #{e.message}", type: :warning)
|
|
1332
|
+
end
|
|
1114
1333
|
end
|
|
1115
1334
|
|
|
1116
1335
|
# Validate changes against guard policy
|
|
@@ -10,23 +10,20 @@ module Aidp
|
|
|
10
10
|
class WorkflowSelector
|
|
11
11
|
include Aidp::MessageDisplay
|
|
12
12
|
|
|
13
|
-
def initialize(prompt: TTY::Prompt.new)
|
|
13
|
+
def initialize(prompt: TTY::Prompt.new, workflow_selector: nil)
|
|
14
14
|
@user_input = {}
|
|
15
15
|
@prompt = prompt
|
|
16
|
-
@workflow_selector = Aidp::Workflows::Selector.new(prompt: @prompt)
|
|
16
|
+
@workflow_selector = workflow_selector || Aidp::Workflows::Selector.new(prompt: @prompt)
|
|
17
17
|
end
|
|
18
18
|
|
|
19
19
|
# Main entry point for interactive workflow selection
|
|
20
|
-
def select_workflow(harness_mode: false,
|
|
20
|
+
def select_workflow(harness_mode: false, mode: nil)
|
|
21
21
|
if harness_mode
|
|
22
22
|
# In harness mode, use default values to avoid blocking
|
|
23
23
|
select_workflow_with_defaults
|
|
24
|
-
|
|
24
|
+
else
|
|
25
25
|
# Use new unified workflow selector (default as of Issue #79)
|
|
26
26
|
select_workflow_with_new_selector(mode)
|
|
27
|
-
else
|
|
28
|
-
# Legacy interactive mode for backward compatibility
|
|
29
|
-
select_workflow_interactive
|
|
30
27
|
end
|
|
31
28
|
end
|
|
32
29
|
|
|
@@ -48,26 +45,6 @@ module Aidp
|
|
|
48
45
|
}
|
|
49
46
|
end
|
|
50
47
|
|
|
51
|
-
def select_workflow_interactive
|
|
52
|
-
display_message("\n🚀 Welcome to AI Dev Pipeline!", type: :highlight)
|
|
53
|
-
display_message("Let's set up your development workflow.\n\n")
|
|
54
|
-
|
|
55
|
-
# Step 1: Collect project information
|
|
56
|
-
collect_project_info
|
|
57
|
-
|
|
58
|
-
# Step 2: Choose workflow type
|
|
59
|
-
workflow_type = choose_workflow_type
|
|
60
|
-
|
|
61
|
-
# Step 3: Generate workflow steps
|
|
62
|
-
steps = generate_workflow_steps(workflow_type)
|
|
63
|
-
|
|
64
|
-
{
|
|
65
|
-
workflow_type: workflow_type,
|
|
66
|
-
steps: steps,
|
|
67
|
-
user_input: @user_input
|
|
68
|
-
}
|
|
69
|
-
end
|
|
70
|
-
|
|
71
48
|
def select_workflow_with_defaults
|
|
72
49
|
display_message("\n🚀 Starting harness with default workflow configuration...", type: :highlight)
|
|
73
50
|
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "yaml"
|
|
4
|
+
require "fileutils"
|
|
5
|
+
require_relative "../providers/base"
|
|
6
|
+
require_relative "../providers/anthropic"
|
|
7
|
+
require_relative "../providers/cursor"
|
|
8
|
+
require_relative "../providers/github_copilot"
|
|
9
|
+
require_relative "../providers/gemini"
|
|
10
|
+
require_relative "../providers/kilocode"
|
|
11
|
+
require_relative "../providers/opencode"
|
|
12
|
+
require_relative "../providers/codex"
|
|
13
|
+
require_relative "../providers/aider"
|
|
14
|
+
|
|
15
|
+
module Aidp
|
|
16
|
+
module Firewall
|
|
17
|
+
# Collects firewall requirements from all provider classes
|
|
18
|
+
# and generates the firewall configuration YAML file
|
|
19
|
+
class ProviderRequirementsCollector
|
|
20
|
+
attr_reader :config_path
|
|
21
|
+
|
|
22
|
+
# Core infrastructure domains not tied to specific providers
|
|
23
|
+
CORE_DOMAINS = {
|
|
24
|
+
ruby: [
|
|
25
|
+
"rubygems.org",
|
|
26
|
+
"api.rubygems.org",
|
|
27
|
+
"index.rubygems.org"
|
|
28
|
+
],
|
|
29
|
+
javascript: [
|
|
30
|
+
"registry.npmjs.org",
|
|
31
|
+
"registry.yarnpkg.com"
|
|
32
|
+
],
|
|
33
|
+
github: [
|
|
34
|
+
"github.com",
|
|
35
|
+
"api.github.com",
|
|
36
|
+
"raw.githubusercontent.com",
|
|
37
|
+
"objects.githubusercontent.com",
|
|
38
|
+
"gist.githubusercontent.com",
|
|
39
|
+
"cloud.githubusercontent.com"
|
|
40
|
+
],
|
|
41
|
+
cdn: [
|
|
42
|
+
"cdn.jsdelivr.net"
|
|
43
|
+
],
|
|
44
|
+
vscode: [
|
|
45
|
+
"update.code.visualstudio.com",
|
|
46
|
+
"marketplace.visualstudio.com",
|
|
47
|
+
"vscode.blob.core.windows.net",
|
|
48
|
+
"vscode.download.prss.microsoft.com",
|
|
49
|
+
"az764295.vo.msecnd.net",
|
|
50
|
+
"gallerycdn.vsassets.io",
|
|
51
|
+
"vscode.gallerycdn.vsassets.io",
|
|
52
|
+
"gallery.vsassets.io",
|
|
53
|
+
"vscode-sync.trafficmanager.net",
|
|
54
|
+
"vscode.dev",
|
|
55
|
+
"go.microsoft.com",
|
|
56
|
+
"download.visualstudio.microsoft.com"
|
|
57
|
+
],
|
|
58
|
+
telemetry: [
|
|
59
|
+
"dc.services.visualstudio.com",
|
|
60
|
+
"vortex.data.microsoft.com"
|
|
61
|
+
]
|
|
62
|
+
}.freeze
|
|
63
|
+
|
|
64
|
+
# Static IP ranges (CIDR notation)
|
|
65
|
+
STATIC_IP_RANGES = [
|
|
66
|
+
{"cidr" => "140.82.112.0/20", "comment" => "GitHub main infrastructure (covers 140.82.112.0 - 140.82.127.255)"},
|
|
67
|
+
{"cidr" => "127.0.0.0/8", "comment" => "Localhost loopback"}
|
|
68
|
+
].freeze
|
|
69
|
+
|
|
70
|
+
# Azure IP ranges for GitHub Copilot and VS Code services
|
|
71
|
+
# These use broader /16 ranges to handle dynamic IP allocation across Azure regions
|
|
72
|
+
AZURE_IP_RANGES = [
|
|
73
|
+
{"cidr" => "20.189.0.0/16", "comment" => "Azure WestUS2 (GitHub Copilot - broad range due to dynamic IP allocation)"},
|
|
74
|
+
{"cidr" => "104.208.0.0/16", "comment" => "Azure EastUS (GitHub Copilot - broad range due to dynamic IP allocation)"},
|
|
75
|
+
{"cidr" => "52.168.0.0/16", "comment" => "Azure EastUS2 (GitHub Copilot - covers .112 and .117, broad range)"},
|
|
76
|
+
{"cidr" => "40.79.0.0/16", "comment" => "Azure WestUS (GitHub Copilot - broad range due to dynamic IP allocation)"},
|
|
77
|
+
{"cidr" => "13.89.0.0/16", "comment" => "Azure EastUS (GitHub Copilot - broad range due to dynamic IP allocation)"},
|
|
78
|
+
{"cidr" => "13.69.0.0/16", "comment" => "Azure (covers .239, broad range due to dynamic IP allocation)"},
|
|
79
|
+
{"cidr" => "13.66.0.0/16", "comment" => "Azure WestUS2 (VS Code sync service - broad range)"},
|
|
80
|
+
{"cidr" => "20.42.0.0/16", "comment" => "Azure WestEurope (covers .65 and .73, broad range)"},
|
|
81
|
+
{"cidr" => "20.50.0.0/16", "comment" => "Azure (covers .80, broad range due to dynamic IP allocation)"}
|
|
82
|
+
].freeze
|
|
83
|
+
|
|
84
|
+
# Dynamic IP sources configuration
|
|
85
|
+
DYNAMIC_SOURCES = {
|
|
86
|
+
github_meta_api: {
|
|
87
|
+
url: "https://api.github.com/meta",
|
|
88
|
+
fields: ["git"],
|
|
89
|
+
comment: "GitHub Git protocol IP ranges (dynamically fetched)"
|
|
90
|
+
}
|
|
91
|
+
}.freeze
|
|
92
|
+
|
|
93
|
+
def initialize(config_path: nil)
|
|
94
|
+
@config_path = config_path || default_config_path
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Collect firewall requirements from all providers
|
|
98
|
+
#
|
|
99
|
+
# @return [Hash] Hash with provider names as keys and requirements as values
|
|
100
|
+
def collect_requirements
|
|
101
|
+
Aidp.log_debug("firewall_collector", "collecting requirements", provider_count: provider_classes.size)
|
|
102
|
+
|
|
103
|
+
requirements = {}
|
|
104
|
+
provider_classes.each do |provider_class|
|
|
105
|
+
provider_name = extract_provider_name(provider_class)
|
|
106
|
+
reqs = provider_class.firewall_requirements
|
|
107
|
+
|
|
108
|
+
requirements[provider_name] = {
|
|
109
|
+
domains: reqs[:domains] || [],
|
|
110
|
+
ip_ranges: reqs[:ip_ranges] || []
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
Aidp.log_debug(
|
|
114
|
+
"firewall_collector",
|
|
115
|
+
"collected requirements",
|
|
116
|
+
provider: provider_name,
|
|
117
|
+
domains: reqs[:domains]&.size || 0,
|
|
118
|
+
ip_ranges: reqs[:ip_ranges]&.size || 0
|
|
119
|
+
)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
requirements
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# Deduplicate and merge provider requirements
|
|
126
|
+
#
|
|
127
|
+
# @param requirements [Hash] Provider requirements hash
|
|
128
|
+
# @return [Hash] Deduplicated requirements with all_domains and all_ip_ranges
|
|
129
|
+
def deduplicate(requirements)
|
|
130
|
+
all_domains = []
|
|
131
|
+
all_ip_ranges = []
|
|
132
|
+
|
|
133
|
+
requirements.each_value do |reqs|
|
|
134
|
+
all_domains.concat(reqs[:domains])
|
|
135
|
+
all_ip_ranges.concat(reqs[:ip_ranges])
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
{
|
|
139
|
+
all_domains: all_domains.uniq.sort,
|
|
140
|
+
all_ip_ranges: all_ip_ranges.uniq.sort,
|
|
141
|
+
by_provider: requirements
|
|
142
|
+
}
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# Generate the complete YAML configuration file
|
|
146
|
+
#
|
|
147
|
+
# Creates a new YAML file with core infrastructure and provider requirements
|
|
148
|
+
#
|
|
149
|
+
# @param dry_run [Boolean] If true, only log what would be generated
|
|
150
|
+
# @return [Boolean] True if generation was successful
|
|
151
|
+
def generate_yaml_config(dry_run: false)
|
|
152
|
+
Aidp.log_info("firewall_collector", "generating config", path: @config_path, dry_run: dry_run)
|
|
153
|
+
|
|
154
|
+
# Collect provider requirements
|
|
155
|
+
provider_requirements = collect_requirements
|
|
156
|
+
deduplicated = deduplicate(provider_requirements)
|
|
157
|
+
|
|
158
|
+
# Build complete configuration
|
|
159
|
+
config = {
|
|
160
|
+
"version" => 1,
|
|
161
|
+
"static_ip_ranges" => STATIC_IP_RANGES,
|
|
162
|
+
"azure_ip_ranges" => AZURE_IP_RANGES,
|
|
163
|
+
"core_domains" => CORE_DOMAINS.transform_keys(&:to_s),
|
|
164
|
+
"provider_domains" => provider_requirements.transform_values { |reqs| reqs[:domains] },
|
|
165
|
+
"dynamic_sources" => DYNAMIC_SOURCES.transform_keys(&:to_s).transform_values do |v|
|
|
166
|
+
v.transform_keys(&:to_s)
|
|
167
|
+
end
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if dry_run
|
|
171
|
+
Aidp.log_info("firewall_collector", "dry run - would generate", domains: deduplicated[:all_domains].size)
|
|
172
|
+
puts "Would generate YAML with:"
|
|
173
|
+
puts " - #{STATIC_IP_RANGES.size} static IP ranges"
|
|
174
|
+
puts " - #{AZURE_IP_RANGES.size} Azure IP ranges"
|
|
175
|
+
puts " - #{CORE_DOMAINS.values.flatten.size} core domains"
|
|
176
|
+
puts " - #{deduplicated[:all_domains].size} provider domains from #{provider_requirements.size} providers"
|
|
177
|
+
return true
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
# Ensure directory exists
|
|
181
|
+
FileUtils.mkdir_p(File.dirname(@config_path))
|
|
182
|
+
|
|
183
|
+
# Write configuration
|
|
184
|
+
File.write(@config_path, YAML.dump(config))
|
|
185
|
+
Aidp.log_info(
|
|
186
|
+
"firewall_collector",
|
|
187
|
+
"config generated",
|
|
188
|
+
path: @config_path,
|
|
189
|
+
providers: provider_requirements.size,
|
|
190
|
+
total_domains: CORE_DOMAINS.values.flatten.size + deduplicated[:all_domains].size
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
true
|
|
194
|
+
rescue => e
|
|
195
|
+
Aidp.log_error("firewall_collector", "generation failed", error: e.message)
|
|
196
|
+
false
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
# Alias for backward compatibility
|
|
200
|
+
alias_method :update_yaml_config, :generate_yaml_config
|
|
201
|
+
|
|
202
|
+
# Generate a summary report of provider requirements
|
|
203
|
+
#
|
|
204
|
+
# @return [String] Formatted summary report
|
|
205
|
+
def generate_report
|
|
206
|
+
requirements = collect_requirements
|
|
207
|
+
deduplicated = deduplicate(requirements)
|
|
208
|
+
|
|
209
|
+
report = []
|
|
210
|
+
report << "Firewall Provider Requirements Summary"
|
|
211
|
+
report << "=" * 50
|
|
212
|
+
report << ""
|
|
213
|
+
report << "Total Providers: #{requirements.size}"
|
|
214
|
+
report << "Total Unique Domains: #{deduplicated[:all_domains].size}"
|
|
215
|
+
report << "Total Unique IP Ranges: #{deduplicated[:all_ip_ranges].size}"
|
|
216
|
+
report << ""
|
|
217
|
+
report << "By Provider:"
|
|
218
|
+
report << "-" * 50
|
|
219
|
+
|
|
220
|
+
requirements.each do |provider, reqs|
|
|
221
|
+
report << ""
|
|
222
|
+
report << "#{provider.capitalize}:"
|
|
223
|
+
report << " Domains (#{reqs[:domains].size}):"
|
|
224
|
+
reqs[:domains].each { |d| report << " - #{d}" }
|
|
225
|
+
if reqs[:ip_ranges].any?
|
|
226
|
+
report << " IP Ranges (#{reqs[:ip_ranges].size}):"
|
|
227
|
+
reqs[:ip_ranges].each { |ip| report << " - #{ip}" }
|
|
228
|
+
end
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
report.join("\n")
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
private
|
|
235
|
+
|
|
236
|
+
# Get list of all provider classes
|
|
237
|
+
def provider_classes
|
|
238
|
+
[
|
|
239
|
+
Aidp::Providers::Anthropic,
|
|
240
|
+
Aidp::Providers::Cursor,
|
|
241
|
+
Aidp::Providers::GithubCopilot,
|
|
242
|
+
Aidp::Providers::Gemini,
|
|
243
|
+
Aidp::Providers::Kilocode,
|
|
244
|
+
Aidp::Providers::Opencode,
|
|
245
|
+
Aidp::Providers::Codex,
|
|
246
|
+
Aidp::Providers::Aider
|
|
247
|
+
]
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
# Extract provider name from class name
|
|
251
|
+
def extract_provider_name(provider_class)
|
|
252
|
+
# Convert Aidp::Providers::GithubCopilot to "github_copilot"
|
|
253
|
+
provider_class.name.split("::").last.gsub(/([a-z\d])([A-Z])/, '\1_\2').downcase
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
# Default path to firewall config file
|
|
257
|
+
def default_config_path
|
|
258
|
+
File.join(Dir.pwd, ".aidp", "firewall-allowlist.yml")
|
|
259
|
+
end
|
|
260
|
+
end
|
|
261
|
+
end
|
|
262
|
+
end
|