aidp 0.21.0 → 0.22.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.
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "fileutils"
3
4
  require_relative "../harness/provider_manager"
4
5
  require_relative "../harness/provider_factory"
5
6
  require_relative "../harness/config_manager"
@@ -32,7 +33,6 @@ module Aidp
32
33
  @provider_manager = Aidp::Harness::ProviderManager.new(@config_manager, prompt: @prompt)
33
34
  @conversation_history = []
34
35
  @user_input = {}
35
- @invalid_planning_responses = 0
36
36
  @verbose = verbose
37
37
  @debug_env = ENV["DEBUG"] == "1" || ENV["DEBUG"] == "2"
38
38
  end
@@ -105,6 +105,7 @@ module Aidp
105
105
 
106
106
  # Ask questions
107
107
  question_response[:questions]&.each do |question|
108
+ puts "\n" + ("─" * 80) if iteration > 1 # Visual separator between Q&A pairs
108
109
  answer = @prompt.ask(question)
109
110
  @conversation_history << {role: "assistant", content: question}
110
111
  @conversation_history << {role: "user", content: answer}
@@ -115,7 +116,8 @@ module Aidp
115
116
 
116
117
  # Guard: break loop after 10 iterations to avoid infinite loop
117
118
  if iteration >= 10
118
- display_message("[ERROR] Planning loop exceeded 10 iterations. Provider may be returning generic responses.", type: :error)
119
+ display_message("[WARNING] Planning loop exceeded 10 iterations. Provider may be returning generic responses.", type: :warning)
120
+ display_message("Continuing with the plan information gathered so far...", type: :info)
119
121
  break
120
122
  end
121
123
  end
@@ -135,7 +137,7 @@ module Aidp
135
137
  end
136
138
 
137
139
  response = call_provider_for_analysis(system_prompt, user_prompt)
138
- parsed = safe_parse_planning_response(response)
140
+ parsed = parse_planning_response(response)
139
141
  # Attach raw response for debug
140
142
  parsed[:raw_response] = response
141
143
  emit_verbose_raw_prompt(system_prompt, user_prompt, response)
@@ -375,53 +377,20 @@ module Aidp
375
377
  json_match = response_text.match(/```json\s*(\{.*?\})\s*```/m) ||
376
378
  response_text.match(/(\{.*\})/m)
377
379
 
378
- return {error: :invalid_format} unless json_match
379
- JSON.parse(json_match[1], symbolize_names: true)
380
- rescue JSON::ParserError
381
- {error: :invalid_format}
382
- end
383
-
384
- # Provides structured fallback sequence when provider keeps returning invalid planning JSON.
385
- # After exceeding sequence length, switches to manual entry question.
386
- def safe_parse_planning_response(response_text)
387
- parsed = parse_planning_response(response_text)
388
- return parsed unless parsed.is_a?(Hash) && parsed[:error] == :invalid_format
389
-
390
- @invalid_planning_responses += 1
391
- fallback_sequence = [
392
- "Provide scope (key features) and primary users.",
393
- "List 3-5 key functional requirements and any technical constraints.",
394
- "Supply any non-functional requirements (performance/security) or type 'skip'."
395
- ]
396
-
397
- if @invalid_planning_responses <= fallback_sequence.size
398
- {complete: false, questions: [fallback_sequence[@invalid_planning_responses - 1]], reasoning: "Fallback due to invalid provider response (format)", error: :fallback}
399
- else
400
- display_message("[ERROR] Provider returned invalid planning JSON #{@invalid_planning_responses} times. Enter combined plan details manually.", type: :error)
401
- {complete: false, questions: ["Enter plan details manually (features; users; requirements; constraints) or type 'skip'"], reasoning: "Manual recovery mode", error: :manual_recovery}
380
+ unless json_match
381
+ raise ConversationError, "Provider returned invalid format: no JSON found in response"
402
382
  end
383
+
384
+ JSON.parse(json_match[1], symbolize_names: true)
385
+ rescue JSON::ParserError => e
386
+ raise ConversationError, "Provider returned invalid JSON: #{e.message}"
403
387
  end
404
388
 
405
389
  def update_plan_from_answer(plan, question, answer)
406
390
  # Simple heuristic-based plan updates
407
391
  # In a more sophisticated implementation, use AI to categorize answers
408
392
 
409
- # IMPORTANT: Check manual recovery sentinel prompt first so it isn't misclassified
410
- # by broader keyword heuristics (e.g., it contains the word 'users').
411
- if question.start_with?("Enter plan details manually")
412
- unless answer.strip.downcase == "skip"
413
- parts = answer.split(/;|\|/).map(&:strip).reject(&:empty?)
414
- features, users, requirements, constraints = parts
415
- plan[:scope][:included] ||= []
416
- plan[:scope][:included] << features if features
417
- plan[:users][:personas] ||= []
418
- plan[:users][:personas] << users if users
419
- plan[:requirements][:functional] ||= []
420
- plan[:requirements][:functional] << requirements if requirements
421
- plan[:constraints][:technical] ||= []
422
- plan[:constraints][:technical] << constraints if constraints
423
- end
424
- elsif question.downcase.include?("scope") || question.downcase.include?("include")
393
+ if question.downcase.include?("scope") || question.downcase.include?("include")
425
394
  plan[:scope][:included] ||= []
426
395
  plan[:scope][:included] << answer
427
396
  elsif question.downcase.include?("user") || question.downcase.include?("who")
@@ -532,7 +501,9 @@ module Aidp
532
501
  Generated by AIDP Plan & Execute workflow on #{Time.now.strftime("%Y-%m-%d")}
533
502
  PRD
534
503
 
535
- File.write(File.join(@project_dir, "docs", "prd.md"), prd_content)
504
+ docs_dir = File.join(@project_dir, "docs")
505
+ FileUtils.mkdir_p(docs_dir) unless Dir.exist?(docs_dir)
506
+ File.write(File.join(docs_dir, "prd.md"), prd_content)
536
507
  end
537
508
 
538
509
  def generate_nfr_from_plan(plan)
@@ -548,7 +519,9 @@ module Aidp
548
519
  Generated by AIDP Plan & Execute workflow on #{Time.now.strftime("%Y-%m-%d")}
549
520
  NFR
550
521
 
551
- File.write(File.join(@project_dir, "docs", "nfrs.md"), nfr_content)
522
+ docs_dir = File.join(@project_dir, "docs")
523
+ FileUtils.mkdir_p(docs_dir) unless Dir.exist?(docs_dir)
524
+ File.write(File.join(docs_dir, "nfrs.md"), nfr_content)
552
525
  end
553
526
 
554
527
  def generate_style_guide_from_plan(plan)
@@ -563,7 +536,9 @@ module Aidp
563
536
  Generated by AIDP Plan & Execute workflow on #{Time.now.strftime("%Y-%m-%d")}
564
537
  STYLE
565
538
 
566
- File.write(File.join(@project_dir, "docs", "LLM_STYLE_GUIDE.md"), style_guide_content)
539
+ docs_dir = File.join(@project_dir, "docs")
540
+ FileUtils.mkdir_p(docs_dir) unless Dir.exist?(docs_dir)
541
+ File.write(File.join(docs_dir, "LLM_STYLE_GUIDE.md"), style_guide_content)
567
542
  end
568
543
 
569
544
  def format_hash_for_doc(hash)
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.21.0
4
+ version: 0.22.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bart Agapinan
@@ -245,6 +245,7 @@ files:
245
245
  - lib/aidp/analyze/tree_sitter_grammar_loader.rb
246
246
  - lib/aidp/analyze/tree_sitter_scan.rb
247
247
  - lib/aidp/cli.rb
248
+ - lib/aidp/cli/devcontainer_commands.rb
248
249
  - lib/aidp/cli/enhanced_input.rb
249
250
  - lib/aidp/cli/first_run_wizard.rb
250
251
  - lib/aidp/cli/issue_importer.rb
@@ -354,6 +355,11 @@ files:
354
355
  - lib/aidp/providers/github_copilot.rb
355
356
  - lib/aidp/providers/opencode.rb
356
357
  - lib/aidp/rescue_logging.rb
358
+ - lib/aidp/safe_directory.rb
359
+ - lib/aidp/setup/devcontainer/backup_manager.rb
360
+ - lib/aidp/setup/devcontainer/generator.rb
361
+ - lib/aidp/setup/devcontainer/parser.rb
362
+ - lib/aidp/setup/devcontainer/port_manager.rb
357
363
  - lib/aidp/setup/wizard.rb
358
364
  - lib/aidp/skills.rb
359
365
  - lib/aidp/skills/composer.rb