aidp 0.17.0 → 0.17.1

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.
@@ -201,79 +201,10 @@ module Aidp
201
201
  goal
202
202
  end
203
203
 
204
- def analyze_user_intent(user_goal)
205
- display_message("\nšŸ” Analyzing your request...", type: :info)
206
-
207
- # Build the system prompt with AIDP capabilities
208
- system_prompt = build_system_prompt
209
-
210
- # Build the user prompt
211
- user_prompt = build_analysis_prompt(user_goal)
212
-
213
- # Call provider to analyze intent
214
- response = call_provider_for_analysis(system_prompt, user_prompt)
215
-
216
- # Parse the response
217
- parse_recommendation(response)
218
- end
219
-
220
- def build_system_prompt
221
- # Try to load from project dir first, fall back to gem's docs
222
- capabilities_path = File.join(@project_dir, "docs", "AIDP_CAPABILITIES.md")
223
- unless File.exist?(capabilities_path)
224
- # Use the gem's copy
225
- gem_root = File.expand_path("../../..", __dir__)
226
- capabilities_path = File.join(gem_root, "docs", "AIDP_CAPABILITIES.md")
227
- end
228
-
229
- capabilities_doc = File.read(capabilities_path)
230
-
231
- <<~PROMPT
232
- You are an expert AI assistant helping users select the right AIDP workflow for their needs.
233
-
234
- Your role:
235
- 1. Understand what the user wants to accomplish
236
- 2. Match their intent to AIDP's capabilities
237
- 3. Recommend the most appropriate workflow
238
- 4. Explain why this workflow fits their needs
239
- 5. Identify if custom steps or templates are needed
240
-
241
- AIDP Capabilities Reference:
242
- #{capabilities_doc}
243
-
244
- Response Format:
245
- Provide a JSON response with:
246
- {
247
- "mode": "analyze|execute|hybrid",
248
- "workflow_key": "specific_workflow_key",
249
- "reasoning": "brief explanation of why this fits",
250
- "additional_steps": ["any", "custom", "steps", "if", "needed"],
251
- "questions": ["any", "clarifying", "questions"],
252
- "confidence": "high|medium|low"
253
- }
254
-
255
- Be concise to preserve the user's context window.
256
- PROMPT
257
- end
258
-
259
- def build_analysis_prompt(user_goal)
260
- <<~PROMPT
261
- User Goal: #{user_goal}
262
-
263
- Analyze this goal and recommend the most appropriate AIDP workflow.
264
- Consider:
265
- - Is this analysis or development?
266
- - What level of rigor is needed?
267
- - Are there any gaps in existing workflows?
268
-
269
- Provide your recommendation in JSON format.
270
- PROMPT
271
- end
272
-
273
204
  def call_provider_for_analysis(system_prompt, user_prompt)
274
205
  attempts = 0
275
206
  max_attempts = (@provider_manager.respond_to?(:configured_providers) ? @provider_manager.configured_providers.size : 2)
276
- max_attempts = 2 if max_attempts < 2 # at least one retry if a fallback exists
207
+ max_attempts = 2 if max_attempts < 2
277
208
 
278
209
  begin
279
210
  attempts += 1
@@ -283,7 +214,6 @@ module Aidp
283
214
  raise ConversationError, "No provider configured for guided workflow"
284
215
  end
285
216
 
286
- # Create provider instance using ProviderFactory
287
217
  provider_factory = Aidp::Harness::ProviderFactory.new(@config_manager)
288
218
  provider = provider_factory.create_provider(provider_name, prompt: @prompt)
289
219
 
@@ -292,7 +222,7 @@ module Aidp
292
222
  end
293
223
 
294
224
  combined_prompt = "#{system_prompt}\n\n#{user_prompt}"
295
- result = provider.send(prompt: combined_prompt)
225
+ result = provider.send_message(prompt: combined_prompt)
296
226
 
297
227
  if result.nil? || result.empty?
298
228
  raise ConversationError, "Provider request failed: empty response"
@@ -319,20 +249,6 @@ module Aidp
319
249
  end
320
250
  end
321
251
 
322
- def parse_recommendation(response_text)
323
- # Extract JSON from response (might be wrapped in markdown code blocks)
324
- json_match = response_text.match(/```json\s*(\{.*?\})\s*```/m) ||
325
- response_text.match(/(\{.*\})/m)
326
-
327
- unless json_match
328
- raise ConversationError, "Could not parse recommendation from response"
329
- end
330
-
331
- JSON.parse(json_match[1], symbolize_names: true)
332
- rescue JSON::ParserError => e
333
- raise ConversationError, "Invalid JSON in recommendation: #{e.message}"
334
- end
335
-
336
252
  def validate_provider_configuration!
337
253
  configured = @provider_manager.configured_providers
338
254
  if configured.nil? || configured.empty?
@@ -349,232 +265,6 @@ module Aidp
349
265
  end
350
266
  end
351
267
 
352
- def present_recommendation(recommendation)
353
- display_message("\n✨ Recommendation", type: :highlight)
354
- display_message("─" * 60, type: :muted)
355
-
356
- mode = recommendation[:mode].to_sym
357
- workflow_key = recommendation[:workflow_key].to_sym
358
-
359
- # Get workflow details
360
- workflow = Definitions.get_workflow(mode, workflow_key)
361
-
362
- unless workflow
363
- # Handle custom workflow or error
364
- return handle_custom_workflow(recommendation)
365
- end
366
-
367
- # Display the recommendation
368
- display_message("Mode: #{mode.to_s.capitalize}", type: :info)
369
- display_message("Workflow: #{workflow[:name]}", type: :info)
370
- display_message("\n#{workflow[:description]}", type: :muted)
371
- display_message("\nReasoning: #{recommendation[:reasoning]}\n", type: :info)
372
-
373
- # Show what's included
374
- display_message("This workflow includes:", type: :highlight)
375
- workflow[:details].each do |detail|
376
- display_message(" • #{detail}", type: :info)
377
- end
378
-
379
- # Handle additional steps if needed
380
- steps = workflow[:steps]
381
- if recommendation[:additional_steps]&.any?
382
- display_message("\nAdditional custom steps recommended:", type: :highlight)
383
- recommendation[:additional_steps].each do |step|
384
- display_message(" • #{step}", type: :info)
385
- end
386
- steps = workflow[:steps] + recommendation[:additional_steps]
387
- end
388
-
389
- # Ask for confirmation
390
- display_message("")
391
- confirmed = @prompt.yes?("Does this workflow fit your needs?")
392
-
393
- if confirmed
394
- {
395
- mode: mode,
396
- workflow_key: workflow_key,
397
- workflow_type: workflow_key,
398
- steps: steps,
399
- user_input: @user_input,
400
- workflow: workflow
401
- }
402
- else
403
- # Offer alternatives
404
- offer_alternatives(mode)
405
- end
406
- end
407
-
408
- def handle_custom_workflow(recommendation)
409
- display_message("\nšŸ’” Custom Workflow Needed", type: :highlight)
410
- display_message("The AI recommends creating a custom workflow:\n", type: :info)
411
- display_message(recommendation[:reasoning], type: :muted)
412
-
413
- if recommendation[:questions]&.any?
414
- display_message("\nLet me gather some more information:\n", type: :highlight)
415
- recommendation[:questions].each do |question|
416
- answer = @prompt.ask(question)
417
- @user_input[question.downcase.gsub(/\s+/, "_").to_sym] = answer
418
- end
419
- end
420
-
421
- # Let user select from available steps
422
- mode = recommendation[:mode].to_sym
423
- display_message("\nLet's build a custom workflow by selecting specific steps:", type: :info)
424
-
425
- steps = select_custom_steps(mode, recommendation[:additional_steps])
426
-
427
- {
428
- mode: mode,
429
- workflow_key: :custom,
430
- workflow_type: :custom,
431
- steps: steps,
432
- user_input: @user_input,
433
- workflow: {
434
- name: "Custom Workflow",
435
- description: recommendation[:reasoning],
436
- details: steps
437
- }
438
- }
439
- end
440
-
441
- def select_custom_steps(mode, suggested_steps = [])
442
- # Get available steps for the mode
443
- spec = case mode
444
- when :analyze
445
- Aidp::Analyze::Steps::SPEC
446
- when :execute
447
- Aidp::Execute::Steps::SPEC
448
- when :hybrid
449
- # Combine both
450
- Aidp::Analyze::Steps::SPEC.merge(Aidp::Execute::Steps::SPEC)
451
- else
452
- {}
453
- end
454
-
455
- step_choices = spec.map do |step_key, step_spec|
456
- {
457
- name: "#{step_key} - #{step_spec["description"]}",
458
- value: step_key
459
- }
460
- end
461
-
462
- # Pre-select suggested steps
463
- default_indices = suggested_steps.map do |step|
464
- step_choices.index { |choice| choice[:value].to_s == step.to_s }
465
- end.compact
466
-
467
- selected_steps = @prompt.multi_select(
468
- "Select steps for your custom workflow:",
469
- step_choices,
470
- default: default_indices,
471
- per_page: 20
472
- )
473
-
474
- selected_steps.empty? ? suggested_steps : selected_steps
475
- end
476
-
477
- def offer_alternatives(current_mode)
478
- display_message("\nšŸ”„ Let's find a better fit", type: :highlight)
479
-
480
- choices = [
481
- {name: "Try a different #{current_mode} workflow", value: :different_workflow},
482
- {name: "Switch to a different mode", value: :different_mode},
483
- {name: "Build a custom workflow", value: :custom},
484
- {name: "Start over", value: :restart}
485
- ]
486
-
487
- choice = @prompt.select("What would you like to do?", choices)
488
-
489
- case choice
490
- when :different_workflow
491
- select_manual_workflow(current_mode)
492
- when :different_mode
493
- # Let user pick mode manually then workflow
494
- new_mode = @prompt.select(
495
- "Select mode:",
496
- {
497
- "šŸ”¬ Analyze Mode" => :analyze,
498
- "šŸ—ļø Execute Mode" => :execute,
499
- "šŸ”€ Hybrid Mode" => :hybrid
500
- }
501
- )
502
- select_manual_workflow(new_mode)
503
- when :custom
504
- select_custom_steps(current_mode)
505
- when :restart
506
- select_workflow # Recursive call to start over
507
- end
508
- end
509
-
510
- def select_manual_workflow(mode)
511
- workflows = Definitions.workflows_for_mode(mode)
512
-
513
- choices = workflows.map do |key, workflow|
514
- {
515
- name: "#{workflow[:icon]} #{workflow[:name]} - #{workflow[:description]}",
516
- value: key
517
- }
518
- end
519
-
520
- selected_key = @prompt.select("Choose a workflow:", choices, per_page: 15)
521
- workflow = workflows[selected_key]
522
-
523
- {
524
- mode: mode,
525
- workflow_key: selected_key,
526
- workflow_type: selected_key,
527
- steps: workflow[:steps],
528
- user_input: @user_input,
529
- workflow: workflow
530
- }
531
- end
532
-
533
- def collect_workflow_details(workflow_selection)
534
- # Collect additional information based on mode
535
- case workflow_selection[:mode]
536
- when :execute
537
- collect_execute_details
538
- when :analyze
539
- # Analyze mode typically doesn't need much user input
540
- @user_input[:analysis_goal] = @user_input[:original_goal]
541
- when :hybrid
542
- collect_execute_details # Hybrid often needs project details
543
- end
544
-
545
- # Update the workflow selection with collected input
546
- workflow_selection[:user_input] = @user_input
547
- end
548
-
549
- def collect_execute_details
550
- return if @user_input[:project_description] # Already collected
551
-
552
- display_message("\nšŸ“ Project Information", type: :highlight)
553
- display_message("Let me gather some details for the PRD:\n", type: :info)
554
-
555
- @user_input[:project_description] = @prompt.ask(
556
- "Describe what you're building (can reference your original goal):",
557
- default: @user_input[:original_goal]
558
- )
559
-
560
- @user_input[:tech_stack] = @prompt.ask(
561
- "Tech stack (e.g., Ruby/Rails, Node.js, Python)? [optional]",
562
- required: false
563
- )
564
-
565
- @user_input[:target_users] = @prompt.ask(
566
- "Who will use this? [optional]",
567
- required: false
568
- )
569
-
570
- @user_input[:success_criteria] = @prompt.ask(
571
- "How will you measure success? [optional]",
572
- required: false
573
- )
574
- end
575
-
576
- # Plan-then-execute helper methods
577
-
578
268
  def build_planning_system_prompt
579
269
  <<~PROMPT
580
270
  You are a planning assistant helping gather requirements through clarifying questions.
@@ -25,11 +25,17 @@ module Aidp
25
25
  keyword_init: true
26
26
  )
27
27
 
28
- def initialize(project_dir: Dir.pwd, max_concurrent: 3)
28
+ # @param project_dir [String] root directory of the project
29
+ # @param max_concurrent [Integer] maximum number of concurrent workstreams (thread pool size)
30
+ # @param runner_factory [Proc] factory that builds a harness runner. Signature: (path, mode, options) => object responding to #run
31
+ def initialize(project_dir: Dir.pwd, max_concurrent: 3, runner_factory: nil)
29
32
  @project_dir = project_dir
30
33
  @max_concurrent = max_concurrent
31
34
  @results = Concurrent::Hash.new
32
35
  @start_times = Concurrent::Hash.new
36
+ @runner_factory = runner_factory || lambda do |path, mode, options|
37
+ Aidp::Harness::Runner.new(path, mode, options)
38
+ end
33
39
  end
34
40
 
35
41
  # Execute multiple workstreams in parallel
@@ -120,7 +126,7 @@ module Aidp
120
126
  Dir.chdir(workstream[:path])
121
127
 
122
128
  # Execute harness
123
- runner = Aidp::Harness::Runner.new(
129
+ runner = @runner_factory.call(
124
130
  workstream[:path],
125
131
  options[:mode] || :execute,
126
132
  options
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.17.0
4
+ version: 0.17.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bart Agapinan