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.
@@ -2,9 +2,10 @@
2
2
 
3
3
  require_relative "../harness/provider_manager"
4
4
  require_relative "../harness/provider_factory"
5
- require_relative "../harness/configuration"
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
- @prompt = prompt || TTY::Prompt.new
21
- @configuration = Aidp::Harness::Configuration.new(project_dir)
22
- @provider_manager = Aidp::Harness::ProviderManager.new(@configuration, prompt: @prompt)
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 choose the right workflow for your needs.\n", type: :info)
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
- # Step 1: Get user's high-level goal
33
- user_goal = get_user_goal
47
+ private
34
48
 
35
- # Step 2: Use AI to analyze intent and recommend workflow
36
- recommendation = analyze_user_intent(user_goal)
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 3: Present recommendation and get confirmation
39
- workflow_selection = present_recommendation(recommendation)
54
+ # Step 1: Iterative planning conversation
55
+ plan = iterative_planning
40
56
 
41
- # Step 4: Collect any additional required information
42
- collect_workflow_details(workflow_selection)
57
+ # Step 2: Identify needed steps based on plan
58
+ needed_steps = identify_steps_from_plan(plan)
43
59
 
44
- workflow_selection
45
- rescue => e
46
- raise ConversationError, "Failed to guide workflow selection: #{e.message}"
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
- private
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
- def get_user_goal
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(@configuration)
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
- unless result[:status] == :success
155
- raise ConversationError, "Provider request failed: #{result[:error]}"
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[:content]
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.12.1
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