aidp 0.17.1 → 0.18.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.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +69 -0
  3. data/lib/aidp/cli.rb +43 -2
  4. data/lib/aidp/config.rb +9 -14
  5. data/lib/aidp/execute/prompt_manager.rb +128 -1
  6. data/lib/aidp/execute/repl_macros.rb +555 -0
  7. data/lib/aidp/execute/work_loop_runner.rb +108 -1
  8. data/lib/aidp/harness/ai_decision_engine.rb +376 -0
  9. data/lib/aidp/harness/capability_registry.rb +273 -0
  10. data/lib/aidp/harness/config_schema.rb +305 -1
  11. data/lib/aidp/harness/configuration.rb +452 -0
  12. data/lib/aidp/harness/enhanced_runner.rb +7 -1
  13. data/lib/aidp/harness/provider_factory.rb +0 -2
  14. data/lib/aidp/harness/runner.rb +7 -1
  15. data/lib/aidp/harness/thinking_depth_manager.rb +335 -0
  16. data/lib/aidp/harness/zfc_condition_detector.rb +395 -0
  17. data/lib/aidp/init/devcontainer_generator.rb +274 -0
  18. data/lib/aidp/init/runner.rb +37 -10
  19. data/lib/aidp/init.rb +1 -0
  20. data/lib/aidp/prompt_optimization/context_composer.rb +286 -0
  21. data/lib/aidp/prompt_optimization/optimizer.rb +335 -0
  22. data/lib/aidp/prompt_optimization/prompt_builder.rb +309 -0
  23. data/lib/aidp/prompt_optimization/relevance_scorer.rb +256 -0
  24. data/lib/aidp/prompt_optimization/source_code_fragmenter.rb +308 -0
  25. data/lib/aidp/prompt_optimization/style_guide_indexer.rb +240 -0
  26. data/lib/aidp/prompt_optimization/template_indexer.rb +250 -0
  27. data/lib/aidp/provider_manager.rb +0 -2
  28. data/lib/aidp/providers/anthropic.rb +19 -0
  29. data/lib/aidp/setup/wizard.rb +299 -4
  30. data/lib/aidp/utils/devcontainer_detector.rb +166 -0
  31. data/lib/aidp/version.rb +1 -1
  32. data/lib/aidp/watch/build_processor.rb +72 -6
  33. data/lib/aidp/watch/repository_client.rb +2 -1
  34. data/lib/aidp.rb +0 -1
  35. data/templates/aidp.yml.example +128 -0
  36. metadata +14 -2
  37. data/lib/aidp/providers/macos_ui.rb +0 -102
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 949670fdea4721406643f4fd8da096e943740effbd836407311b812350919b14
4
- data.tar.gz: 8034a3c798571e4626b273c4a9abe9a5da38cef5dd4f25de8794a0ef63bf6022
3
+ metadata.gz: 95269030b5c3fe91c2cfb6e29066a4f37afbcf1903e930a377ec02dd1e8a738c
4
+ data.tar.gz: 6c343d45e804a73751c99e0e63491703c1eb9ba18fb2acf4e828198e774c2c92
5
5
  SHA512:
6
- metadata.gz: cdabc6ba04d06a89dbc4ad19b27a64862e960daa350029db4fada5a4f6d32d3fba27cb32dda291871a79ad8f319d27e7dd1348ddcb7fdb8b0f0a1390b9493fc1
7
- data.tar.gz: 39c384f4c01bf00bef550ac9190d27ddd268437be92fc4cdfcdeac3dff09c79c055b2e5b5582e553af6fa838b3f904cce354e9db62ee3638f658eec05673e4f5
6
+ metadata.gz: dc54a6f0b9424c18ebdd8e6df75e77f0f233db98dd59543a9dd6152eacd835d8820c437ccd220c3dbcfb43cf91bfeb47e40ce415773e8cd0e24783770b2ac033
7
+ data.tar.gz: f89cf387121e7edc3c384cead9369bc6ae5be97e639969fa036978a249779152ccd7a50832897be97d7fd04f4083ccf744e23e93c1117c6ed5d1cc7d8529615d
data/README.md CHANGED
@@ -42,6 +42,75 @@ You can re-run the wizard manually:
42
42
  aidp --setup-config
43
43
  ```
44
44
 
45
+ ## Devcontainer Support
46
+
47
+ AIDP provides first-class devcontainer support for sandboxed, secure AI agent execution. Devcontainers offer:
48
+
49
+ - **Network Security**: Strict firewall with allowlisted domains only
50
+ - **Sandboxed Environment**: Isolated from your host system
51
+ - **Elevated Permissions**: AI agents can run with full permissions inside the container
52
+ - **Consistent Setup**: Same environment across all developers
53
+
54
+ ### For AIDP Development
55
+
56
+ This repository includes a `.devcontainer/` setup for developing AIDP itself:
57
+
58
+ ```bash
59
+ # Open in VS Code
60
+ code .
61
+
62
+ # Press F1 → "Dev Containers: Reopen in Container"
63
+ # Container builds automatically with Ruby 3.4.5, all tools, and firewall
64
+
65
+ # Run tests inside container
66
+ bundle exec rspec
67
+
68
+ # Run AIDP inside container
69
+ bundle exec aidp
70
+ ```
71
+
72
+ See [.devcontainer/README.md](.devcontainer/README.md) for complete documentation.
73
+
74
+ ### Generating Devcontainers for Your Projects
75
+
76
+ Use `aidp init` to generate a devcontainer for any project:
77
+
78
+ ```bash
79
+ # Initialize project with devcontainer
80
+ aidp init
81
+
82
+ # When prompted:
83
+ # "Generate devcontainer configuration for sandboxed development?" → Yes
84
+
85
+ # Or use the flag directly
86
+ aidp init --with-devcontainer
87
+ ```
88
+
89
+ This creates:
90
+
91
+ - `.devcontainer/Dockerfile` - Customized for your project's language/framework
92
+ - `.devcontainer/devcontainer.json` - VS Code configuration and extensions
93
+ - `.devcontainer/init-firewall.sh` - Network security rules
94
+ - `.devcontainer/README.md` - Setup and usage documentation
95
+
96
+ ### Elevated Permissions in Devcontainers
97
+
98
+ When running inside a devcontainer, you can enable elevated permissions for AI agents:
99
+
100
+ ```yaml
101
+ # aidp.yml
102
+ devcontainer:
103
+ enabled: true
104
+ full_permissions_when_in_devcontainer: true # Run all providers with full permissions
105
+
106
+ # Or enable per-provider
107
+ permissions:
108
+ skip_permission_checks:
109
+ - claude # Adds --dangerously-skip-permissions for Claude Code
110
+ ```
111
+
112
+ AIDP automatically detects when it's running in a devcontainer and adjusts agent permissions accordingly. This is safe because the container is sandboxed from your host system.
113
+
45
114
  ## Core Features
46
115
 
47
116
  ### Work Loops
data/lib/aidp/cli.rb CHANGED
@@ -759,9 +759,10 @@ module Aidp
759
759
  require_relative "harness/provider_info"
760
760
 
761
761
  provider_name = args.shift
762
+
763
+ # If no provider specified, show models catalog table
762
764
  unless provider_name
763
- display_message("Usage: aidp providers info <provider_name>", type: :info)
764
- display_message("Example: aidp providers info claude", type: :info)
765
+ run_providers_models_catalog
765
766
  return
766
767
  end
767
768
 
@@ -839,6 +840,46 @@ module Aidp
839
840
  display_message("Tip: Use --refresh to update this information", type: :muted)
840
841
  end
841
842
 
843
+ def run_providers_models_catalog
844
+ require_relative "harness/capability_registry"
845
+ require "tty-table"
846
+
847
+ display_message("Models Catalog - Thinking Depth Tiers", type: :highlight)
848
+ display_message("=" * 80, type: :muted)
849
+
850
+ registry = Aidp::Harness::CapabilityRegistry.new
851
+ unless registry.load_catalog
852
+ display_message("No models catalog found. Create .aidp/models_catalog.yml first.", type: :error)
853
+ return
854
+ end
855
+
856
+ rows = []
857
+ registry.provider_names.sort.each do |provider|
858
+ models = registry.models_for_provider(provider)
859
+ models.each do |model_name, model_data|
860
+ tier = model_data["tier"] || "-"
861
+ context = model_data["context_window"] ? "#{model_data["context_window"] / 1000}k" : "-"
862
+ tools = model_data["supports_tools"] ? "yes" : "no"
863
+ cost_input = model_data["cost_per_mtok_input"]
864
+ cost = cost_input ? "$#{cost_input}/MTok" : "-"
865
+
866
+ rows << [provider, model_name, tier, context, tools, cost]
867
+ end
868
+ end
869
+
870
+ if rows.empty?
871
+ display_message("No models found in catalog", type: :info)
872
+ return
873
+ end
874
+
875
+ header = ["Provider", "Model", "Tier", "Context", "Tools", "Cost"]
876
+ table = TTY::Table.new(header, rows)
877
+ display_message(table.render(:basic), type: :info)
878
+
879
+ display_message("\n" + "=" * 80, type: :muted)
880
+ display_message("Use '/thinking show' in REPL to see current tier configuration", type: :muted)
881
+ end
882
+
842
883
  def run_providers_refresh_command(args)
843
884
  require_relative "harness/provider_info"
844
885
  require "tty-spinner"
data/lib/aidp/config.rb CHANGED
@@ -15,8 +15,7 @@ module Aidp
15
15
  no_api_keys_required: false,
16
16
  provider_weights: {
17
17
  "cursor" => 3,
18
- "anthropic" => 2,
19
- "macos" => 1
18
+ "anthropic" => 2
20
19
  },
21
20
  circuit_breaker: {
22
21
  enabled: true,
@@ -74,6 +73,7 @@ module Aidp
74
73
  cursor: {
75
74
  type: "subscription",
76
75
  priority: 1,
76
+ model_family: "auto",
77
77
  default_flags: [],
78
78
  models: ["cursor-default", "cursor-fast", "cursor-precise"],
79
79
  model_weights: {
@@ -108,6 +108,7 @@ module Aidp
108
108
  anthropic: {
109
109
  type: "usage_based",
110
110
  priority: 2,
111
+ model_family: "claude",
111
112
  max_tokens: 100_000,
112
113
  default_flags: ["--dangerously-skip-permissions"],
113
114
  models: ["claude-3-5-sonnet-20241022", "claude-3-5-haiku-20241022", "claude-3-opus-20240229"],
@@ -149,18 +150,6 @@ module Aidp
149
150
  enabled: true,
150
151
  metrics_interval: 60
151
152
  }
152
- },
153
- macos: {
154
- type: "passthrough",
155
- priority: 4,
156
- underlying_service: "cursor",
157
- models: ["cursor-chat"],
158
- features: {
159
- file_upload: false,
160
- code_generation: true,
161
- analysis: true,
162
- interactive: true
163
- }
164
153
  }
165
154
  },
166
155
  skills: {
@@ -342,6 +331,12 @@ module Aidp
342
331
  merged[:skills] = merged[:skills].merge(symbolize_keys(skills_section))
343
332
  end
344
333
 
334
+ # Deep merge thinking config
335
+ if config[:thinking] || config["thinking"]
336
+ thinking_section = config[:thinking] || config["thinking"]
337
+ merged[:thinking] = symbolize_keys(thinking_section)
338
+ end
339
+
345
340
  merged
346
341
  end
347
342
 
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "fileutils"
4
+ require_relative "../prompt_optimization/optimizer"
4
5
 
5
6
  module Aidp
6
7
  module Execute
@@ -9,21 +10,91 @@ module Aidp
9
10
  # - Read/write PROMPT.md
10
11
  # - Check existence
11
12
  # - Archive completed prompts
13
+ # - Optionally optimize prompts using intelligent fragment selection (ZFC)
12
14
  class PromptManager
13
15
  PROMPT_FILENAME = "PROMPT.md"
14
16
  ARCHIVE_DIR = ".aidp/prompt_archive"
15
17
 
16
- def initialize(project_dir)
18
+ attr_reader :optimizer, :last_optimization_stats
19
+
20
+ def initialize(project_dir, config: nil)
17
21
  @project_dir = project_dir
18
22
  @prompt_path = File.join(project_dir, PROMPT_FILENAME)
19
23
  @archive_dir = File.join(project_dir, ARCHIVE_DIR)
24
+ @config = config
25
+ @optimizer = nil
26
+ @last_optimization_stats = nil
27
+
28
+ # Initialize optimizer if enabled
29
+ if config&.respond_to?(:prompt_optimization_enabled?) && config.prompt_optimization_enabled?
30
+ @optimizer = Aidp::PromptOptimization::Optimizer.new(
31
+ project_dir: project_dir,
32
+ config: config.prompt_optimization_config
33
+ )
34
+ end
20
35
  end
21
36
 
22
37
  # Write content to PROMPT.md
38
+ # If optimization is enabled, stores the content but doesn't write yet
39
+ # (use write_optimized instead)
23
40
  def write(content)
24
41
  File.write(@prompt_path, content)
25
42
  end
26
43
 
44
+ # Write optimized prompt using intelligent fragment selection
45
+ #
46
+ # Uses Zero Framework Cognition to select only the most relevant fragments
47
+ # from style guides, templates, and source code based on task context.
48
+ #
49
+ # @param task_context [Hash] Context about the current task
50
+ # @option task_context [Symbol] :task_type Type of task (:feature, :bugfix, etc.)
51
+ # @option task_context [String] :description Task description
52
+ # @option task_context [Array<String>] :affected_files Files being modified
53
+ # @option task_context [String] :step_name Current work loop step
54
+ # @option task_context [Array<String>] :tags Additional context tags
55
+ # @param options [Hash] Optimization options
56
+ # @option options [Boolean] :include_metadata Include debug metadata
57
+ # @return [Boolean] True if optimization was used, false if fallback to regular write
58
+ def write_optimized(task_context, options = {})
59
+ unless @optimizer
60
+ Aidp.logger.warn("prompt_manager", "Optimization requested but not enabled")
61
+ return false
62
+ end
63
+
64
+ begin
65
+ # Use optimizer to build intelligent prompt
66
+ result = @optimizer.optimize_prompt(
67
+ task_type: task_context[:task_type],
68
+ description: task_context[:description],
69
+ affected_files: task_context[:affected_files] || [],
70
+ step_name: task_context[:step_name],
71
+ tags: task_context[:tags] || [],
72
+ options: options
73
+ )
74
+
75
+ # Write optimized prompt
76
+ result.write_to_file(@prompt_path)
77
+
78
+ # Store statistics for inspection
79
+ @last_optimization_stats = result.composition_result
80
+
81
+ # Log optimization results
82
+ Aidp.logger.info(
83
+ "prompt_manager",
84
+ "Optimized prompt written",
85
+ selected_fragments: result.composition_result.selected_count,
86
+ excluded_fragments: result.composition_result.excluded_count,
87
+ tokens: result.estimated_tokens,
88
+ budget_utilization: result.composition_result.budget_utilization
89
+ )
90
+
91
+ true
92
+ rescue => e
93
+ Aidp.logger.error("prompt_manager", "Optimization failed, using fallback", error: e.message)
94
+ false
95
+ end
96
+ end
97
+
27
98
  # Read content from PROMPT.md
28
99
  def read
29
100
  return nil unless exists?
@@ -57,6 +128,62 @@ module Aidp
57
128
  def path
58
129
  @prompt_path
59
130
  end
131
+
132
+ # Get optimization report for last optimization
133
+ #
134
+ # @return [String, nil] Markdown report or nil if no optimization performed
135
+ def optimization_report
136
+ return nil unless @last_optimization_stats
137
+
138
+ # Build report from composition result
139
+ lines = []
140
+ lines << "# Prompt Optimization Report"
141
+ lines << ""
142
+ lines << "## Statistics"
143
+ lines << "- **Selected Fragments**: #{@last_optimization_stats.selected_count}"
144
+ lines << "- **Excluded Fragments**: #{@last_optimization_stats.excluded_count}"
145
+ lines << "- **Total Tokens**: #{@last_optimization_stats.total_tokens} / #{@last_optimization_stats.budget}"
146
+ lines << "- **Budget Utilization**: #{@last_optimization_stats.budget_utilization.round(1)}%"
147
+ lines << "- **Average Relevance Score**: #{(@last_optimization_stats.average_score * 100).round(1)}%"
148
+ lines << ""
149
+ lines << "## Selected Fragments"
150
+ @last_optimization_stats.selected_fragments.each do |scored|
151
+ fragment = scored[:fragment]
152
+ score = scored[:score]
153
+ lines << "- #{fragment_name(fragment)} (#{(score * 100).round(0)}%)"
154
+ end
155
+
156
+ lines.join("\n")
157
+ end
158
+
159
+ # Check if optimization is enabled
160
+ #
161
+ # @return [Boolean] True if optimizer is available
162
+ def optimization_enabled?
163
+ !@optimizer.nil?
164
+ end
165
+
166
+ # Get optimizer statistics
167
+ #
168
+ # @return [Hash, nil] Statistics hash or nil if optimizer not available
169
+ def optimizer_stats
170
+ @optimizer&.statistics
171
+ end
172
+
173
+ private
174
+
175
+ # Get human-readable name for a fragment
176
+ def fragment_name(fragment)
177
+ if fragment.respond_to?(:heading)
178
+ fragment.heading
179
+ elsif fragment.respond_to?(:name)
180
+ fragment.name
181
+ elsif fragment.respond_to?(:id)
182
+ fragment.id
183
+ else
184
+ "Unknown fragment"
185
+ end
186
+ end
60
187
  end
61
188
  end
62
189
  end