aidp 0.39.9 → 0.40.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 48e9a4d5c62e99992a0b61d3547b20b2a7598c13dbc6ba8b23a4f8126cd74c35
4
- data.tar.gz: 9427e78702251d548a938d5b93f466b9514686128162c4be5bb52eb8375013a0
3
+ metadata.gz: 2414ec5ab6a21100ba8ccb1d6ae8ddc6bb137a5403b069ff941707c17c9ce9fa
4
+ data.tar.gz: fb0401b3daf671719690b5c5347418eab335e8090dac38ebe08aef43a921430d
5
5
  SHA512:
6
- metadata.gz: eb8f0d861f8b85f8df71034a2e6cbef875be34dcf28eacec009aaa65c8589527b904fa060598b43c9854e63c9b354ca0299bb70b96eb5da80e36c19b6ea0a033
7
- data.tar.gz: a46a2734b9094b966c242a284b1673cd956df4281294557b813925d19f8204bdf470ad93d4a1c01e27a2846de2b6544258d062f0f36dacd29607827a7bc3049c
6
+ metadata.gz: c033c034fed453eb4daa2108bb653f3da5007e0d49a792d4a3d9b4f976d63de3c796647da98d3131ea4b81d41425b7aea19ab4e94e743b2eb59936eea9dcad59
7
+ data.tar.gz: 70ebc3d5e2f809e21398321b7b26c315309a849a35ab4be5742d58650a123a4826c69e64c0b9e8fbc0aef7402da7a99a9970c0d49e83e516aab436e57187d974
data/README.md CHANGED
@@ -42,6 +42,37 @@ You can re-run the wizard manually:
42
42
  aidp --setup-config
43
43
  ```
44
44
 
45
+ ## Run With Docker
46
+
47
+ Use the standalone Docker image when you want Aidp without a local Ruby
48
+ toolchain. Build the image once, then mount any project into `/workspace`.
49
+
50
+ ```bash
51
+ docker build -t aidp:local .
52
+
53
+ docker run --rm -it \
54
+ --env-file .env \
55
+ -v "$(pwd)":/workspace \
56
+ -v "$(pwd)/.aidp":/workspace/.aidp \
57
+ aidp:local
58
+ ```
59
+
60
+ Watch mode uses the same image:
61
+
62
+ ```bash
63
+ docker run --rm -it \
64
+ --env-file .env \
65
+ -v "$(pwd)":/workspace \
66
+ -v "$(pwd)/.aidp":/workspace/.aidp \
67
+ aidp:local watch https://github.com/<owner>/<repo>/issues
68
+ ```
69
+
70
+ If you supply `GH_TOKEN` or `GITHUB_TOKEN`, the image can use the bundled `gh`
71
+ CLI for read-only access to private repos. Public repositories continue to work
72
+ without GitHub credentials. See [docs/DOCKER_QUICKSTART.md](docs/DOCKER_QUICKSTART.md)
73
+ for environment variables, wrapper scripts, network options, and multi-arch
74
+ `buildx` examples.
75
+
45
76
  ## Devcontainer Support
46
77
 
47
78
  AIDP provides first-class devcontainer support for sandboxed, secure AI agent execution. Devcontainers offer:
@@ -116,7 +147,7 @@ aidp devcontainer list-backups
116
147
  aidp devcontainer restore 0
117
148
  ```
118
149
 
119
- See [docs/how-to/DEVELOPMENT_CONTAINER.md](docs/how-to/DEVELOPMENT_CONTAINER.md) for complete devcontainer management documentation.
150
+ See [docs/how-to/DEVELOPMENT_CONTAINER.md](docs/how-to/DEVELOPMENT_CONTAINER.md) for complete devcontainer management documentation. If you do not want VS Code or a local Ruby toolchain, see [Run With Docker](#run-with-docker).
120
151
 
121
152
  ### Devcontainer Detection
122
153
 
data/lib/aidp/cli.rb CHANGED
@@ -347,6 +347,7 @@ module Aidp
347
347
  opts.on("-v", "--version", "Show version information") { options[:version] = true }
348
348
  opts.on("--setup-config", "Setup or reconfigure config file") { options[:setup_config] = true }
349
349
  opts.on("--verbose", "Show detailed prompts and raw provider responses during guided workflow") { options[:verbose] = true }
350
+ opts.on("--style STYLE", "Override PRD style for guided planning (detailed, balanced, quick_sketch)") { |style| options[:style] = style }
350
351
  opts.on("--quiet", "Suppress non-critical output (incompatible with --verbose and --interactive)") { options[:quiet] = true }
351
352
  # Undocumented: Quick exit launch test for CI/CD validation
352
353
  opts.on("--launch-test", nil) { options[:launch_test] = true }
@@ -10,6 +10,16 @@ module Aidp
10
10
  class ConfigManager
11
11
  include ProviderTypeChecker
12
12
 
13
+ DEFAULT_PRD_GENERATION = {
14
+ interaction_style: "balanced",
15
+ default_style: "balanced",
16
+ max_question_rounds: {
17
+ detailed: 30,
18
+ balanced: 10,
19
+ quick_sketch: 3
20
+ }
21
+ }.freeze
22
+
13
23
  attr_reader :project_dir
14
24
 
15
25
  def initialize(project_dir = Dir.pwd)
@@ -195,6 +205,24 @@ module Aidp
195
205
  }
196
206
  end
197
207
 
208
+ def prd_generation_config(options = {})
209
+ config = config(options)
210
+ raw_config = config&.dig(:prd_generation) || config&.dig("prd_generation") || {}
211
+ raw_rounds = raw_config[:max_question_rounds] || raw_config["max_question_rounds"] || {}
212
+
213
+ {
214
+ interaction_style: raw_config[:interaction_style] || raw_config["interaction_style"] || DEFAULT_PRD_GENERATION[:interaction_style],
215
+ default_style: raw_config[:default_style] || raw_config["default_style"] || DEFAULT_PRD_GENERATION[:default_style],
216
+ max_question_rounds: DEFAULT_PRD_GENERATION[:max_question_rounds].merge(
217
+ symbolize_keys(raw_rounds)
218
+ )
219
+ }
220
+ end
221
+
222
+ def prd_interaction_style(options = {})
223
+ prd_generation_config(options)[:interaction_style]
224
+ end
225
+
198
226
  # Get provider models
199
227
  def provider_models(provider_name, options = {})
200
228
  provider_config = provider_config(provider_name, options)
@@ -351,6 +379,12 @@ module Aidp
351
379
 
352
380
  private
353
381
 
382
+ def symbolize_keys(hash)
383
+ hash.each_with_object({}) do |(key, value), memo|
384
+ memo[key.to_sym] = value
385
+ end
386
+ end
387
+
354
388
  def load_config_with_options(options)
355
389
  # Apply different loading strategies based on options
356
390
  if options[:mode]
@@ -237,10 +237,12 @@ module Aidp
237
237
  def select_guided_workflow
238
238
  # Use the guided agent to help user select workflow
239
239
  # Don't pass prompt so it uses EnhancedInput with full readline support
240
- verbose_flag = (defined?(Aidp::CLI) && Aidp::CLI.respond_to?(:last_options) && Aidp::CLI.last_options) ? Aidp::CLI.last_options[:verbose] : false
240
+ cli_options = (defined?(Aidp::CLI) && Aidp::CLI.respond_to?(:last_options) && Aidp::CLI.last_options) ? Aidp::CLI.last_options : {}
241
+ verbose_flag = cli_options[:verbose]
242
+ interaction_style = cli_options[:style]
241
243
  # Fallback: store verbose in an env for easier access if options not available
242
244
  verbose = verbose_flag || ENV["AIDP_VERBOSE"] == "1"
243
- guided_agent = Aidp::Workflows::GuidedAgent.new(@project_dir, verbose: verbose)
245
+ guided_agent = Aidp::Workflows::GuidedAgent.new(@project_dir, verbose: verbose, interaction_style: interaction_style)
244
246
  result = guided_agent.select_workflow
245
247
 
246
248
  # Store user input for later use
@@ -23,6 +23,16 @@ module Aidp
23
23
  LEGACY_TIER_ALIASES = {
24
24
  advanced: :pro
25
25
  }.freeze
26
+ PRD_STYLE_OPTIONS = {
27
+ "Detailed - deep questioning, longer sessions" => "detailed",
28
+ "Balanced - moderate questioning (recommended)" => "balanced",
29
+ "Quick Sketch - minimal questions, Aidp fills gaps automatically" => "quick_sketch"
30
+ }.freeze
31
+ DEFAULT_PRD_QUESTION_ROUNDS = {
32
+ detailed: 30,
33
+ balanced: 10,
34
+ quick_sketch: 3
35
+ }.freeze
26
36
 
27
37
  attr_reader :project_dir, :prompt, :dry_run
28
38
  # Expose state for testability
@@ -49,6 +59,7 @@ module Aidp
49
59
  configure_harness_settings
50
60
  configure_thinking_tiers
51
61
  configure_work_loop
62
+ configure_prd_generation
52
63
  configure_branching
53
64
  configure_artifacts
54
65
  configure_nfrs
@@ -492,6 +503,27 @@ module Aidp
492
503
  configure_vcs_behavior
493
504
  end
494
505
 
506
+ def configure_prd_generation
507
+ prompt.say("\n🧭 PRD generation style")
508
+ prompt.say("-" * 40)
509
+
510
+ existing = get(%i[prd_generation]) || {}
511
+ current_style = existing[:interaction_style] || existing[:default_style] || "balanced"
512
+ default_label = PRD_STYLE_OPTIONS.key(current_style) || PRD_STYLE_OPTIONS.key("balanced")
513
+
514
+ selected_style = prompt.select("Choose PRD interaction style:", default: default_label) do |menu|
515
+ PRD_STYLE_OPTIONS.each do |label, value|
516
+ menu.choice label, value
517
+ end
518
+ end
519
+
520
+ set(%i[prd_generation], {
521
+ interaction_style: selected_style,
522
+ default_style: selected_style,
523
+ max_question_rounds: DEFAULT_PRD_QUESTION_ROUNDS
524
+ })
525
+ end
526
+
495
527
  # Configure generic deterministic commands for work loop
496
528
  # Replaces the old category-specific configure_test_commands and configure_linting
497
529
  def configure_commands
@@ -1913,6 +1945,7 @@ module Aidp
1913
1945
  .sub(/^schema_version:/, "# Tracks configuration migrations\nschema_version:")
1914
1946
  .sub(/^providers:/, "# Provider configuration (no secrets stored)\nproviders:")
1915
1947
  .sub(/^work_loop:/, "# Work loop execution settings\nwork_loop:")
1948
+ .sub(/^prd_generation:/, "# PRD questioning depth and autonomy\nprd_generation:")
1916
1949
  .sub(/^nfrs:/, "# Non-functional requirements to reference during planning\nnfrs:")
1917
1950
  .sub(/^logging:/, "# Logging configuration\nlogging:")
1918
1951
  .sub(/^modes:/, "# Defaults for background/watch/quick modes\nmodes:")
data/lib/aidp/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Aidp
4
- VERSION = "0.39.9"
4
+ VERSION = "0.40.0"
5
5
  end
@@ -19,11 +19,32 @@ module Aidp
19
19
 
20
20
  class ConversationError < StandardError; end
21
21
 
22
+ PLANNING_STYLES = {
23
+ "detailed" => {
24
+ label: "Detailed",
25
+ max_question_rounds: 30,
26
+ autonomy: "low",
27
+ question_strategy: "Ask 1-2 sharply scoped questions per round. Avoid assumptions unless the user confirms them."
28
+ },
29
+ "balanced" => {
30
+ label: "Balanced",
31
+ max_question_rounds: 10,
32
+ autonomy: "medium",
33
+ question_strategy: "Ask 1-3 efficient questions per round. Make reasonable decisions when the context is already clear."
34
+ },
35
+ "quick_sketch" => {
36
+ label: "Quick Sketch",
37
+ max_question_rounds: 3,
38
+ autonomy: "high",
39
+ question_strategy: "Ask only the highest-leverage missing questions. Prefer inferring obvious MVP details from repository context and existing configuration."
40
+ }
41
+ }.freeze
42
+
22
43
  # Expose for testability
23
44
  attr_reader :project_dir, :conversation_history, :user_input, :config_manager
24
45
  attr_writer :provider_manager
25
46
 
26
- def initialize(project_dir, prompt: nil, use_enhanced_input: true, verbose: false, config_manager: nil, provider_manager: nil)
47
+ def initialize(project_dir, prompt: nil, use_enhanced_input: true, verbose: false, interaction_style: nil, config_manager: nil, provider_manager: nil)
27
48
  @project_dir = project_dir
28
49
 
29
50
  # Use EnhancedInput with Reline for full readline-style key bindings
@@ -39,6 +60,7 @@ module Aidp
39
60
  @user_input = {}
40
61
  @verbose = verbose
41
62
  @debug_env = debug_level >= Aidp::DebugMixin::DEBUG_BASIC
63
+ @planning_preferences = resolve_planning_preferences(interaction_style)
42
64
  end
43
65
 
44
66
  # Main entry point for guided workflow selection
@@ -60,6 +82,7 @@ module Aidp
60
82
  # Plan-and-execute: iterative planning followed by execution
61
83
  def plan_and_execute_workflow
62
84
  display_message("\n📋 Plan Phase", type: :highlight)
85
+ display_message("PRD style: #{planning_style[:interaction_style]}", type: :info)
63
86
  display_message("I'll ask clarifying questions to understand your needs.\n", type: :info)
64
87
 
65
88
  # Step 1: Iterative planning conversation
@@ -77,11 +100,25 @@ module Aidp
77
100
 
78
101
  def iterative_planning
79
102
  goal = user_goal
80
- plan = {goal: goal, scope: {}, users: {}, requirements: {}, constraints: {}, completion_criteria: []}
103
+ plan = {
104
+ goal: goal,
105
+ scope: {},
106
+ users: {},
107
+ requirements: {},
108
+ constraints: {},
109
+ completion_criteria: [],
110
+ assumptions: [],
111
+ metadata: {
112
+ interaction_style: planning_style[:interaction_style],
113
+ autonomy: planning_style[:autonomy],
114
+ max_question_rounds: planning_style[:max_question_rounds]
115
+ }
116
+ }
81
117
 
82
118
  @conversation_history << {role: "user", content: goal}
83
119
 
84
120
  iteration = 0
121
+ max_rounds = planning_style[:max_question_rounds]
85
122
  loop do
86
123
  iteration += 1
87
124
  # Ask AI for next question based on current plan
@@ -96,6 +133,7 @@ module Aidp
96
133
 
97
134
  # If AI says plan is complete, confirm with user
98
135
  if question_response[:complete]
136
+ plan[:assumptions].concat(Array(question_response[:assumptions])).uniq!
99
137
  display_message("\n✅ Plan Summary", type: :highlight)
100
138
  display_plan_summary(plan)
101
139
 
@@ -118,14 +156,14 @@ module Aidp
118
156
  update_plan_from_answer(plan, question, answer)
119
157
  end
120
158
 
121
- # Guard: break loop after 10 iterations to avoid infinite loop
122
- if iteration >= 10
123
- display_message("[WARNING] Planning loop exceeded 10 iterations. Provider may be returning generic responses.", type: :warning)
159
+ if iteration >= max_rounds
160
+ display_message("[WARNING] Planning loop exceeded #{max_rounds} iterations for #{planning_style[:interaction_style]}.", type: :warning)
124
161
  display_message("Continuing with the plan information gathered so far...", type: :info)
125
162
  break
126
163
  end
127
164
  end
128
165
 
166
+ apply_style_assumptions(plan)
129
167
  plan
130
168
  end
131
169
 
@@ -142,6 +180,7 @@ module Aidp
142
180
 
143
181
  response = call_provider_for_analysis(system_prompt, user_prompt)
144
182
  parsed = parse_planning_response(response)
183
+ parsed[:assumptions] = Array(parsed[:assumptions]).compact
145
184
  # Attach raw response for debug
146
185
  parsed[:raw_response] = response
147
186
  emit_verbose_raw_prompt(system_prompt, user_prompt, response)
@@ -397,16 +436,19 @@ module Aidp
397
436
  You are a planning assistant helping gather requirements through clarifying questions.
398
437
 
399
438
  Your role:
400
- 1. Ask 1-3 targeted questions at a time based on what's known
439
+ 1. #{planning_style[:question_strategy]}
401
440
  2. Build towards a complete understanding of: scope, users, requirements, constraints
402
441
  3. Determine when enough information has been gathered
403
442
  4. Be concise to preserve context window
443
+ 5. Operate in "#{planning_style[:interaction_style]}" mode with #{planning_style[:autonomy]} autonomy
444
+ 6. When you make an assumption, list it explicitly in "assumptions"
404
445
 
405
446
  Response Format (JSON):
406
447
  {
407
448
  "complete": true/false,
408
449
  "questions": ["question 1", "question 2"],
409
- "reasoning": "brief explanation of what you're trying to learn"
450
+ "reasoning": "brief explanation of what you're trying to learn",
451
+ "assumptions": ["assumption 1"]
410
452
  }
411
453
 
412
454
  If complete is true, the plan is ready for execution.
@@ -470,6 +512,28 @@ module Aidp
470
512
  end
471
513
  end
472
514
 
515
+ def apply_style_assumptions(plan)
516
+ inferred_assumptions = []
517
+
518
+ if plan[:users].empty?
519
+ plan[:users][:personas] = [default_persona_for_style]
520
+ inferred_assumptions << "Primary user persona inferred as #{default_persona_for_style}."
521
+ end
522
+
523
+ if plan[:completion_criteria].empty?
524
+ criterion = default_completion_criterion
525
+ plan[:completion_criteria] << criterion
526
+ inferred_assumptions << "Success metric inferred as #{criterion}"
527
+ end
528
+
529
+ inferred_assumptions.concat(repository_default_assumptions)
530
+ plan[:assumptions].concat(inferred_assumptions).uniq!
531
+
532
+ inferred_assumptions.each do |assumption|
533
+ display_message("Inferred missing detail: #{assumption}", type: :muted)
534
+ end
535
+ end
536
+
473
537
  def display_plan_summary(plan)
474
538
  display_message("Goal: #{plan[:goal]}", type: :info)
475
539
  display_message("\nScope:", type: :highlight) if plan[:scope].any?
@@ -533,6 +597,10 @@ module Aidp
533
597
  prd_content = <<~PRD
534
598
  # Product Requirements Document
535
599
 
600
+ ## Planning Metadata
601
+ - PRD Style: #{plan.dig(:metadata, :interaction_style) || planning_style[:interaction_style]}
602
+ - Agent Autonomy: #{plan.dig(:metadata, :autonomy) || planning_style[:autonomy]}
603
+
536
604
  ## Goal
537
605
  #{plan[:goal]}
538
606
 
@@ -551,6 +619,9 @@ module Aidp
551
619
  ## Completion Criteria
552
620
  #{plan[:completion_criteria].map { |c| "- #{c}" }.join("\n")}
553
621
 
622
+ ## Assumptions & Inferred Details
623
+ #{plan[:assumptions]&.any? ? plan[:assumptions].map { |item| "- #{item}" }.join("\n") : "None"}
624
+
554
625
  ## Additional Context
555
626
  #{plan[:additional_context]&.map { |ctx| "**#{ctx[:question]}**: #{ctx[:answer]}" }&.join("\n\n")}
556
627
 
@@ -611,6 +682,64 @@ module Aidp
611
682
  end
612
683
  end.join("\n\n")
613
684
  end
685
+
686
+ def planning_style
687
+ @planning_preferences
688
+ end
689
+
690
+ def resolve_planning_preferences(override_style)
691
+ config = @config_manager.respond_to?(:prd_generation_config) ? @config_manager.prd_generation_config : {}
692
+ configured_style = override_style || config[:interaction_style] || config["interaction_style"]
693
+ interaction_style = normalize_interaction_style(configured_style || "balanced")
694
+ configured_rounds = config[:max_question_rounds] || config["max_question_rounds"] || {}
695
+ defaults = PLANNING_STYLES.fetch(interaction_style)
696
+
697
+ defaults.merge(
698
+ interaction_style: interaction_style,
699
+ max_question_rounds: configured_rounds[interaction_style.to_sym] || configured_rounds[interaction_style] || defaults[:max_question_rounds]
700
+ )
701
+ end
702
+
703
+ def normalize_interaction_style(style)
704
+ normalized = style.to_s.strip.downcase.tr(" ", "_")
705
+ return normalized if PLANNING_STYLES.key?(normalized)
706
+
707
+ "balanced"
708
+ end
709
+
710
+ def default_persona_for_style
711
+ if planning_style[:interaction_style] == "quick_sketch"
712
+ "Prototype users validating the MVP flow"
713
+ else
714
+ "Primary end users for this feature"
715
+ end
716
+ end
717
+
718
+ def default_completion_criterion
719
+ if planning_style[:interaction_style] == "quick_sketch"
720
+ "A thin MVP slice is implemented and demoable end-to-end."
721
+ else
722
+ "The feature meets the documented requirements and is ready for implementation planning."
723
+ end
724
+ end
725
+
726
+ def repository_default_assumptions
727
+ config = @config_manager.respond_to?(:config) ? (@config_manager.config || {}) : {}
728
+ assumptions = []
729
+ commands = config.dig(:work_loop, :commands) || config.dig("work_loop", "commands")
730
+ nfrs = config.dig(:nfrs) || config.dig("nfrs")
731
+
732
+ if commands.is_a?(Array) && commands.any?
733
+ command_label = (commands.size == 1) ? "command" : "commands"
734
+ assumptions << "Validation will reuse #{commands.size} configured work loop #{command_label}."
735
+ end
736
+
737
+ if nfrs.is_a?(Hash) && nfrs.any?
738
+ assumptions << "Repository-level non-functional requirements from aidp.yml will apply."
739
+ end
740
+
741
+ assumptions
742
+ end
614
743
  end
615
744
  end
616
745
  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.39.9
4
+ version: 0.40.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bart Agapinan