aidp 0.12.1 ā 0.13.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/lib/aidp/analyze/json_file_storage.rb +21 -21
- data/lib/aidp/cli/enhanced_input.rb +114 -0
- data/lib/aidp/cli/first_run_wizard.rb +1 -7
- data/lib/aidp/cli/mcp_dashboard.rb +3 -3
- data/lib/aidp/cli/terminal_io.rb +26 -0
- data/lib/aidp/cli.rb +4 -4
- data/lib/aidp/harness/condition_detector.rb +6 -6
- data/lib/aidp/harness/config_loader.rb +23 -23
- data/lib/aidp/harness/config_manager.rb +61 -61
- data/lib/aidp/harness/config_validator.rb +9 -9
- data/lib/aidp/harness/configuration.rb +28 -28
- data/lib/aidp/harness/error_handler.rb +13 -13
- data/lib/aidp/harness/provider_config.rb +79 -79
- data/lib/aidp/harness/provider_factory.rb +40 -40
- data/lib/aidp/harness/provider_info.rb +37 -20
- data/lib/aidp/harness/provider_manager.rb +58 -53
- data/lib/aidp/harness/provider_type_checker.rb +6 -6
- data/lib/aidp/harness/runner.rb +7 -7
- data/lib/aidp/harness/status_display.rb +33 -46
- data/lib/aidp/harness/ui/enhanced_workflow_selector.rb +2 -1
- data/lib/aidp/harness/ui/job_monitor.rb +7 -7
- data/lib/aidp/harness/user_interface.rb +43 -43
- data/lib/aidp/providers/anthropic.rb +100 -26
- data/lib/aidp/providers/base.rb +13 -0
- data/lib/aidp/providers/codex.rb +28 -27
- data/lib/aidp/providers/cursor.rb +141 -34
- data/lib/aidp/providers/github_copilot.rb +26 -26
- data/lib/aidp/providers/macos_ui.rb +2 -18
- data/lib/aidp/providers/opencode.rb +26 -26
- data/lib/aidp/version.rb +1 -1
- data/lib/aidp/workflows/guided_agent.rb +344 -23
- metadata +2 -1
@@ -2,9 +2,10 @@
|
|
2
2
|
|
3
3
|
require_relative "../harness/provider_manager"
|
4
4
|
require_relative "../harness/provider_factory"
|
5
|
-
require_relative "../harness/
|
5
|
+
require_relative "../harness/config_manager"
|
6
6
|
require_relative "definitions"
|
7
7
|
require_relative "../message_display"
|
8
|
+
require_relative "../cli/enhanced_input"
|
8
9
|
|
9
10
|
module Aidp
|
10
11
|
module Workflows
|
@@ -15,40 +16,141 @@ module Aidp
|
|
15
16
|
|
16
17
|
class ConversationError < StandardError; end
|
17
18
|
|
18
|
-
def initialize(project_dir, prompt: nil)
|
19
|
+
def initialize(project_dir, prompt: nil, use_enhanced_input: true)
|
19
20
|
@project_dir = project_dir
|
20
|
-
|
21
|
-
|
22
|
-
@
|
21
|
+
|
22
|
+
# Use EnhancedInput with Reline for full readline-style key bindings
|
23
|
+
@prompt = if use_enhanced_input && prompt.nil?
|
24
|
+
Aidp::CLI::EnhancedInput.new
|
25
|
+
else
|
26
|
+
prompt || TTY::Prompt.new
|
27
|
+
end
|
28
|
+
|
29
|
+
@config_manager = Aidp::Harness::ConfigManager.new(project_dir)
|
30
|
+
@provider_manager = Aidp::Harness::ProviderManager.new(@config_manager, prompt: @prompt)
|
23
31
|
@conversation_history = []
|
24
32
|
@user_input = {}
|
25
33
|
end
|
26
34
|
|
27
35
|
# Main entry point for guided workflow selection
|
36
|
+
# Uses plan-then-execute approach: iterative planning conversation
|
37
|
+
# to identify needed steps, then executes those steps
|
28
38
|
def select_workflow
|
29
39
|
display_message("\nš¤ Welcome to AIDP Guided Workflow!", type: :highlight)
|
30
|
-
display_message("I'll help you
|
40
|
+
display_message("I'll help you plan and execute your project.\n", type: :info)
|
41
|
+
|
42
|
+
plan_and_execute_workflow
|
43
|
+
rescue => e
|
44
|
+
raise ConversationError, "Failed to guide workflow selection: #{e.message}"
|
45
|
+
end
|
31
46
|
|
32
|
-
|
33
|
-
user_goal = get_user_goal
|
47
|
+
private
|
34
48
|
|
35
|
-
|
36
|
-
|
49
|
+
# Plan-and-execute: iterative planning followed by execution
|
50
|
+
def plan_and_execute_workflow
|
51
|
+
display_message("\nš Plan Phase", type: :highlight)
|
52
|
+
display_message("I'll ask clarifying questions to understand your needs.\n", type: :info)
|
37
53
|
|
38
|
-
# Step
|
39
|
-
|
54
|
+
# Step 1: Iterative planning conversation
|
55
|
+
plan = iterative_planning
|
40
56
|
|
41
|
-
# Step
|
42
|
-
|
57
|
+
# Step 2: Identify needed steps based on plan
|
58
|
+
needed_steps = identify_steps_from_plan(plan)
|
43
59
|
|
44
|
-
|
45
|
-
|
46
|
-
|
60
|
+
# Step 3: Generate planning documents from plan
|
61
|
+
generate_documents_from_plan(plan)
|
62
|
+
|
63
|
+
# Step 4: Build workflow selection
|
64
|
+
build_workflow_from_plan(plan, needed_steps)
|
47
65
|
end
|
48
66
|
|
49
|
-
|
67
|
+
def iterative_planning
|
68
|
+
goal = user_goal
|
69
|
+
plan = {goal: goal, scope: {}, users: {}, requirements: {}, constraints: {}, completion_criteria: []}
|
70
|
+
|
71
|
+
@conversation_history << {role: "user", content: goal}
|
72
|
+
|
73
|
+
loop do
|
74
|
+
# Ask AI for next question based on current plan
|
75
|
+
question_response = get_planning_questions(plan)
|
76
|
+
|
77
|
+
# If AI says plan is complete, confirm with user
|
78
|
+
if question_response[:complete]
|
79
|
+
display_message("\nā
Plan Summary", type: :highlight)
|
80
|
+
display_plan_summary(plan)
|
50
81
|
|
51
|
-
|
82
|
+
if @prompt.yes?("\nIs this plan ready for execution?")
|
83
|
+
break
|
84
|
+
else
|
85
|
+
# Continue planning
|
86
|
+
question_response = {questions: ["What would you like to add or clarify?"]}
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# Ask questions
|
91
|
+
question_response[:questions]&.each do |question|
|
92
|
+
answer = @prompt.ask(question)
|
93
|
+
@conversation_history << {role: "assistant", content: question}
|
94
|
+
@conversation_history << {role: "user", content: answer}
|
95
|
+
|
96
|
+
# Update plan with answer
|
97
|
+
update_plan_from_answer(plan, question, answer)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
plan
|
102
|
+
end
|
103
|
+
|
104
|
+
def get_planning_questions(plan)
|
105
|
+
system_prompt = build_planning_system_prompt
|
106
|
+
user_prompt = build_planning_prompt(plan)
|
107
|
+
|
108
|
+
response = call_provider_for_analysis(system_prompt, user_prompt)
|
109
|
+
parse_planning_response(response)
|
110
|
+
end
|
111
|
+
|
112
|
+
def identify_steps_from_plan(plan)
|
113
|
+
display_message("\nš Identifying needed steps...", type: :info)
|
114
|
+
|
115
|
+
system_prompt = build_step_identification_prompt
|
116
|
+
user_prompt = build_plan_summary_for_step_identification(plan)
|
117
|
+
|
118
|
+
response = call_provider_for_analysis(system_prompt, user_prompt)
|
119
|
+
parse_step_identification(response)
|
120
|
+
end
|
121
|
+
|
122
|
+
def generate_documents_from_plan(plan)
|
123
|
+
display_message("\nš Generating planning documents...", type: :info)
|
124
|
+
|
125
|
+
# Generate PRD
|
126
|
+
generate_prd_from_plan(plan)
|
127
|
+
|
128
|
+
# Generate NFRs if applicable
|
129
|
+
generate_nfr_from_plan(plan) if plan.dig(:requirements, :non_functional)
|
130
|
+
|
131
|
+
# Generate style guide if applicable
|
132
|
+
generate_style_guide_from_plan(plan) if plan[:style_requirements]
|
133
|
+
|
134
|
+
display_message(" ā Documents generated", type: :success)
|
135
|
+
end
|
136
|
+
|
137
|
+
def build_workflow_from_plan(plan, needed_steps)
|
138
|
+
{
|
139
|
+
mode: :execute,
|
140
|
+
workflow_key: :plan_and_execute,
|
141
|
+
workflow_type: :plan_and_execute,
|
142
|
+
steps: needed_steps,
|
143
|
+
user_input: @user_input.merge(plan: plan),
|
144
|
+
workflow: {
|
145
|
+
name: "Plan & Execute",
|
146
|
+
description: "Custom workflow from iterative planning",
|
147
|
+
details: needed_steps.map { |step| Aidp::Execute::Steps::SPEC[step]["description"] }
|
148
|
+
},
|
149
|
+
completion_criteria: plan[:completion_criteria]
|
150
|
+
}
|
151
|
+
end
|
152
|
+
|
153
|
+
def user_goal
|
52
154
|
display_message("What would you like to do?", type: :highlight)
|
53
155
|
display_message("Examples:", type: :muted)
|
54
156
|
display_message(" ⢠Build a new feature for user authentication", type: :muted)
|
@@ -139,7 +241,7 @@ module Aidp
|
|
139
241
|
end
|
140
242
|
|
141
243
|
# Create provider instance using ProviderFactory
|
142
|
-
provider_factory = Aidp::Harness::ProviderFactory.new(@
|
244
|
+
provider_factory = Aidp::Harness::ProviderFactory.new(@config_manager)
|
143
245
|
provider = provider_factory.create_provider(provider_name, prompt: @prompt)
|
144
246
|
|
145
247
|
unless provider
|
@@ -151,11 +253,13 @@ module Aidp
|
|
151
253
|
|
152
254
|
result = provider.send(prompt: combined_prompt)
|
153
255
|
|
154
|
-
|
155
|
-
|
256
|
+
# Provider.send returns a string (the content), not a hash
|
257
|
+
# Check if result is nil or empty which indicates failure
|
258
|
+
if result.nil? || result.empty?
|
259
|
+
raise ConversationError, "Provider request failed: empty response"
|
156
260
|
end
|
157
261
|
|
158
|
-
result
|
262
|
+
result
|
159
263
|
end
|
160
264
|
|
161
265
|
def parse_recommendation(response_text)
|
@@ -395,6 +499,223 @@ module Aidp
|
|
395
499
|
required: false
|
396
500
|
)
|
397
501
|
end
|
502
|
+
|
503
|
+
# Plan-then-execute helper methods
|
504
|
+
|
505
|
+
def build_planning_system_prompt
|
506
|
+
<<~PROMPT
|
507
|
+
You are a planning assistant helping gather requirements through clarifying questions.
|
508
|
+
|
509
|
+
Your role:
|
510
|
+
1. Ask 1-3 targeted questions at a time based on what's known
|
511
|
+
2. Build towards a complete understanding of: scope, users, requirements, constraints
|
512
|
+
3. Determine when enough information has been gathered
|
513
|
+
4. Be concise to preserve context window
|
514
|
+
|
515
|
+
Response Format (JSON):
|
516
|
+
{
|
517
|
+
"complete": true/false,
|
518
|
+
"questions": ["question 1", "question 2"],
|
519
|
+
"reasoning": "brief explanation of what you're trying to learn"
|
520
|
+
}
|
521
|
+
|
522
|
+
If complete is true, the plan is ready for execution.
|
523
|
+
PROMPT
|
524
|
+
end
|
525
|
+
|
526
|
+
def build_planning_prompt(plan)
|
527
|
+
<<~PROMPT
|
528
|
+
Current Plan:
|
529
|
+
Goal: #{plan[:goal]}
|
530
|
+
Scope: #{plan[:scope].inspect}
|
531
|
+
Users: #{plan[:users].inspect}
|
532
|
+
Requirements: #{plan[:requirements].inspect}
|
533
|
+
Constraints: #{plan[:constraints].inspect}
|
534
|
+
Completion Criteria: #{plan[:completion_criteria].inspect}
|
535
|
+
|
536
|
+
Conversation History:
|
537
|
+
#{@conversation_history.map { |msg| "#{msg[:role]}: #{msg[:content]}" }.join("\n")}
|
538
|
+
|
539
|
+
Based on this plan, determine if you have enough information or what clarifying questions to ask next.
|
540
|
+
PROMPT
|
541
|
+
end
|
542
|
+
|
543
|
+
def parse_planning_response(response_text)
|
544
|
+
json_match = response_text.match(/```json\s*(\{.*?\})\s*```/m) ||
|
545
|
+
response_text.match(/(\{.*\})/m)
|
546
|
+
|
547
|
+
unless json_match
|
548
|
+
return {complete: false, questions: ["Could you tell me more about your requirements?"]}
|
549
|
+
end
|
550
|
+
|
551
|
+
JSON.parse(json_match[1], symbolize_names: true)
|
552
|
+
rescue JSON::ParserError
|
553
|
+
{complete: false, questions: ["Could you tell me more about your requirements?"]}
|
554
|
+
end
|
555
|
+
|
556
|
+
def update_plan_from_answer(plan, question, answer)
|
557
|
+
# Simple heuristic-based plan updates
|
558
|
+
# In a more sophisticated implementation, use AI to categorize answers
|
559
|
+
|
560
|
+
if question.downcase.include?("scope") || question.downcase.include?("include")
|
561
|
+
plan[:scope][:included] ||= []
|
562
|
+
plan[:scope][:included] << answer
|
563
|
+
elsif question.downcase.include?("user") || question.downcase.include?("who")
|
564
|
+
plan[:users][:personas] ||= []
|
565
|
+
plan[:users][:personas] << answer
|
566
|
+
elsif question.downcase.include?("requirement") || question.downcase.include?("feature")
|
567
|
+
plan[:requirements][:functional] ||= []
|
568
|
+
plan[:requirements][:functional] << answer
|
569
|
+
elsif question.downcase.include?("performance") || question.downcase.include?("security") || question.downcase.include?("scalability")
|
570
|
+
plan[:requirements][:non_functional] ||= {}
|
571
|
+
plan[:requirements][:non_functional][question] = answer
|
572
|
+
elsif question.downcase.include?("constraint") || question.downcase.include?("limitation")
|
573
|
+
plan[:constraints][:technical] ||= []
|
574
|
+
plan[:constraints][:technical] << answer
|
575
|
+
elsif question.downcase.include?("complete") || question.downcase.include?("done") || question.downcase.include?("success")
|
576
|
+
plan[:completion_criteria] << answer
|
577
|
+
else
|
578
|
+
# General information
|
579
|
+
plan[:additional_context] ||= []
|
580
|
+
plan[:additional_context] << {question: question, answer: answer}
|
581
|
+
end
|
582
|
+
end
|
583
|
+
|
584
|
+
def display_plan_summary(plan)
|
585
|
+
display_message("Goal: #{plan[:goal]}", type: :info)
|
586
|
+
display_message("\nScope:", type: :highlight) if plan[:scope].any?
|
587
|
+
plan[:scope].each { |k, v| display_message(" #{k}: #{v}", type: :muted) }
|
588
|
+
display_message("\nUsers:", type: :highlight) if plan[:users].any?
|
589
|
+
plan[:users].each { |k, v| display_message(" #{k}: #{v}", type: :muted) }
|
590
|
+
display_message("\nRequirements:", type: :highlight) if plan[:requirements].any?
|
591
|
+
plan[:requirements].each { |k, v| display_message(" #{k}: #{v}", type: :muted) }
|
592
|
+
display_message("\nCompletion Criteria:", type: :highlight) if plan[:completion_criteria].any?
|
593
|
+
plan[:completion_criteria].each { |c| display_message(" ⢠#{c}", type: :info) }
|
594
|
+
end
|
595
|
+
|
596
|
+
def build_step_identification_prompt
|
597
|
+
all_steps = Aidp::Execute::Steps::SPEC.map do |key, spec|
|
598
|
+
"#{key}: #{spec["description"]}"
|
599
|
+
end.join("\n")
|
600
|
+
|
601
|
+
<<~PROMPT
|
602
|
+
You are an expert at identifying which AIDP workflow steps are needed for a project.
|
603
|
+
|
604
|
+
Available Execute Steps:
|
605
|
+
#{all_steps}
|
606
|
+
|
607
|
+
Based on the plan provided, identify which steps are needed and in what order.
|
608
|
+
|
609
|
+
Response Format (JSON):
|
610
|
+
{
|
611
|
+
"steps": ["00_PRD", "02_ARCHITECTURE", "16_IMPLEMENTATION"],
|
612
|
+
"reasoning": "brief explanation of why these steps"
|
613
|
+
}
|
614
|
+
|
615
|
+
Be concise and select only the necessary steps.
|
616
|
+
PROMPT
|
617
|
+
end
|
618
|
+
|
619
|
+
def build_plan_summary_for_step_identification(plan)
|
620
|
+
<<~PROMPT
|
621
|
+
Plan Summary:
|
622
|
+
#{plan.to_json}
|
623
|
+
|
624
|
+
Which execute steps are needed for this plan?
|
625
|
+
PROMPT
|
626
|
+
end
|
627
|
+
|
628
|
+
def parse_step_identification(response_text)
|
629
|
+
json_match = response_text.match(/```json\s*(\{.*?\})\s*```/m) ||
|
630
|
+
response_text.match(/(\{.*\})/m)
|
631
|
+
|
632
|
+
unless json_match
|
633
|
+
# Fallback to basic workflow
|
634
|
+
return ["00_PRD", "16_IMPLEMENTATION"]
|
635
|
+
end
|
636
|
+
|
637
|
+
parsed = JSON.parse(json_match[1], symbolize_names: true)
|
638
|
+
parsed[:steps] || ["00_PRD", "16_IMPLEMENTATION"]
|
639
|
+
rescue JSON::ParserError
|
640
|
+
["00_PRD", "16_IMPLEMENTATION"]
|
641
|
+
end
|
642
|
+
|
643
|
+
def generate_prd_from_plan(plan)
|
644
|
+
prd_content = <<~PRD
|
645
|
+
# Product Requirements Document
|
646
|
+
|
647
|
+
## Goal
|
648
|
+
#{plan[:goal]}
|
649
|
+
|
650
|
+
## Scope
|
651
|
+
#{format_hash_for_doc(plan[:scope])}
|
652
|
+
|
653
|
+
## Users & Personas
|
654
|
+
#{format_hash_for_doc(plan[:users])}
|
655
|
+
|
656
|
+
## Requirements
|
657
|
+
#{format_hash_for_doc(plan[:requirements])}
|
658
|
+
|
659
|
+
## Constraints
|
660
|
+
#{format_hash_for_doc(plan[:constraints])}
|
661
|
+
|
662
|
+
## Completion Criteria
|
663
|
+
#{plan[:completion_criteria].map { |c| "- #{c}" }.join("\n")}
|
664
|
+
|
665
|
+
## Additional Context
|
666
|
+
#{plan[:additional_context]&.map { |ctx| "**#{ctx[:question]}**: #{ctx[:answer]}" }&.join("\n\n")}
|
667
|
+
|
668
|
+
---
|
669
|
+
Generated by AIDP Plan & Execute workflow on #{Time.now.strftime("%Y-%m-%d")}
|
670
|
+
PRD
|
671
|
+
|
672
|
+
File.write(File.join(@project_dir, "docs", "prd.md"), prd_content)
|
673
|
+
end
|
674
|
+
|
675
|
+
def generate_nfr_from_plan(plan)
|
676
|
+
nfr_data = plan.dig(:requirements, :non_functional)
|
677
|
+
return unless nfr_data
|
678
|
+
|
679
|
+
nfr_content = <<~NFR
|
680
|
+
# Non-Functional Requirements
|
681
|
+
|
682
|
+
#{nfr_data.map { |k, v| "## #{k}\n#{v}" }.join("\n\n")}
|
683
|
+
|
684
|
+
---
|
685
|
+
Generated by AIDP Plan & Execute workflow on #{Time.now.strftime("%Y-%m-%d")}
|
686
|
+
NFR
|
687
|
+
|
688
|
+
File.write(File.join(@project_dir, "docs", "nfrs.md"), nfr_content)
|
689
|
+
end
|
690
|
+
|
691
|
+
def generate_style_guide_from_plan(plan)
|
692
|
+
return unless plan[:style_requirements]
|
693
|
+
|
694
|
+
style_guide_content = <<~STYLE
|
695
|
+
# LLM Style Guide
|
696
|
+
|
697
|
+
#{plan[:style_requirements]}
|
698
|
+
|
699
|
+
---
|
700
|
+
Generated by AIDP Plan & Execute workflow on #{Time.now.strftime("%Y-%m-%d")}
|
701
|
+
STYLE
|
702
|
+
|
703
|
+
File.write(File.join(@project_dir, "docs", "LLM_STYLE_GUIDE.md"), style_guide_content)
|
704
|
+
end
|
705
|
+
|
706
|
+
def format_hash_for_doc(hash)
|
707
|
+
return "None specified" if hash.nil? || hash.empty?
|
708
|
+
|
709
|
+
hash.map do |key, value|
|
710
|
+
if value.is_a?(Array)
|
711
|
+
"### #{key.to_s.capitalize}\n#{value.map { |v| "- #{v}" }.join("\n")}"
|
712
|
+
elsif value.is_a?(Hash)
|
713
|
+
"### #{key.to_s.capitalize}\n#{value.map { |k, v| "- **#{k}**: #{v}" }.join("\n")}"
|
714
|
+
else
|
715
|
+
"### #{key.to_s.capitalize}\n#{value}"
|
716
|
+
end
|
717
|
+
end.join("\n\n")
|
718
|
+
end
|
398
719
|
end
|
399
720
|
end
|
400
721
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: aidp
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.13.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Bart Agapinan
|
@@ -248,6 +248,7 @@ files:
|
|
248
248
|
- lib/aidp/analyze/tree_sitter_scan.rb
|
249
249
|
- lib/aidp/cli.rb
|
250
250
|
- lib/aidp/cli/checkpoint_command.rb
|
251
|
+
- lib/aidp/cli/enhanced_input.rb
|
251
252
|
- lib/aidp/cli/first_run_wizard.rb
|
252
253
|
- lib/aidp/cli/jobs_command.rb
|
253
254
|
- lib/aidp/cli/mcp_dashboard.rb
|