aidp 0.17.1 → 0.19.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 (40) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +69 -0
  3. data/lib/aidp/cli/terminal_io.rb +5 -2
  4. data/lib/aidp/cli.rb +43 -2
  5. data/lib/aidp/config.rb +9 -14
  6. data/lib/aidp/execute/agent_signal_parser.rb +20 -0
  7. data/lib/aidp/execute/persistent_tasklist.rb +220 -0
  8. data/lib/aidp/execute/prompt_manager.rb +128 -1
  9. data/lib/aidp/execute/repl_macros.rb +719 -0
  10. data/lib/aidp/execute/work_loop_runner.rb +162 -1
  11. data/lib/aidp/harness/ai_decision_engine.rb +376 -0
  12. data/lib/aidp/harness/capability_registry.rb +273 -0
  13. data/lib/aidp/harness/config_schema.rb +305 -1
  14. data/lib/aidp/harness/configuration.rb +452 -0
  15. data/lib/aidp/harness/enhanced_runner.rb +7 -1
  16. data/lib/aidp/harness/provider_factory.rb +0 -2
  17. data/lib/aidp/harness/runner.rb +7 -1
  18. data/lib/aidp/harness/thinking_depth_manager.rb +335 -0
  19. data/lib/aidp/harness/zfc_condition_detector.rb +395 -0
  20. data/lib/aidp/init/devcontainer_generator.rb +274 -0
  21. data/lib/aidp/init/runner.rb +37 -10
  22. data/lib/aidp/init.rb +1 -0
  23. data/lib/aidp/prompt_optimization/context_composer.rb +286 -0
  24. data/lib/aidp/prompt_optimization/optimizer.rb +335 -0
  25. data/lib/aidp/prompt_optimization/prompt_builder.rb +309 -0
  26. data/lib/aidp/prompt_optimization/relevance_scorer.rb +256 -0
  27. data/lib/aidp/prompt_optimization/source_code_fragmenter.rb +308 -0
  28. data/lib/aidp/prompt_optimization/style_guide_indexer.rb +240 -0
  29. data/lib/aidp/prompt_optimization/template_indexer.rb +250 -0
  30. data/lib/aidp/provider_manager.rb +0 -2
  31. data/lib/aidp/providers/anthropic.rb +19 -0
  32. data/lib/aidp/setup/wizard.rb +299 -4
  33. data/lib/aidp/utils/devcontainer_detector.rb +166 -0
  34. data/lib/aidp/version.rb +1 -1
  35. data/lib/aidp/watch/build_processor.rb +72 -6
  36. data/lib/aidp/watch/repository_client.rb +2 -1
  37. data/lib/aidp.rb +1 -1
  38. data/templates/aidp.yml.example +128 -0
  39. metadata +15 -2
  40. data/lib/aidp/providers/macos_ui.rb +0 -102
@@ -0,0 +1,273 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "yaml"
4
+ require "fileutils"
5
+
6
+ module Aidp
7
+ module Harness
8
+ # Stores and queries model capability metadata from the catalog
9
+ # Provides information about model tiers, features, costs, and context windows
10
+ class CapabilityRegistry
11
+ # Valid thinking depth tiers
12
+ VALID_TIERS = %w[mini standard thinking pro max].freeze
13
+
14
+ # Tier priority for escalation (lower index = lower tier)
15
+ TIER_PRIORITY = {
16
+ "mini" => 0,
17
+ "standard" => 1,
18
+ "thinking" => 2,
19
+ "pro" => 3,
20
+ "max" => 4
21
+ }.freeze
22
+
23
+ attr_reader :catalog_path
24
+
25
+ def initialize(catalog_path: nil, root_dir: nil)
26
+ @root_dir = root_dir || Dir.pwd
27
+ @catalog_path = catalog_path || default_catalog_path
28
+ @catalog_data = nil
29
+ @loaded_at = nil
30
+ end
31
+
32
+ # Load catalog from YAML file
33
+ def load_catalog
34
+ return false unless File.exist?(@catalog_path)
35
+
36
+ @catalog_data = YAML.safe_load_file(
37
+ @catalog_path,
38
+ permitted_classes: [Symbol],
39
+ symbolize_names: false
40
+ )
41
+ @loaded_at = Time.now
42
+
43
+ validate_catalog(@catalog_data)
44
+ Aidp.log_debug("capability_registry", "Loaded catalog", path: @catalog_path, providers: provider_names.size)
45
+ true
46
+ rescue => e
47
+ Aidp.log_error("capability_registry", "Failed to load catalog", error: e.message, path: @catalog_path)
48
+ @catalog_data = nil
49
+ false
50
+ end
51
+
52
+ # Get catalog data (lazy load if needed)
53
+ def catalog
54
+ load_catalog if @catalog_data.nil?
55
+ @catalog_data || default_empty_catalog
56
+ end
57
+
58
+ # Get all provider names in catalog
59
+ def provider_names
60
+ catalog.dig("providers")&.keys || []
61
+ end
62
+
63
+ # Get all models for a provider
64
+ def models_for_provider(provider_name)
65
+ provider_data = catalog.dig("providers", provider_name)
66
+ return {} unless provider_data
67
+
68
+ provider_data["models"] || {}
69
+ end
70
+
71
+ # Get tier for a specific model
72
+ def tier_for_model(provider_name, model_name)
73
+ model_data = model_info(provider_name, model_name)
74
+ return nil unless model_data
75
+
76
+ model_data["tier"]
77
+ end
78
+
79
+ # Get all models matching a specific tier
80
+ # Returns hash: { provider_name => [model_name, ...] }
81
+ def models_by_tier(tier, provider: nil)
82
+ validate_tier!(tier)
83
+
84
+ results = {}
85
+ providers_to_search = provider ? [provider] : provider_names
86
+
87
+ providers_to_search.each do |provider_name|
88
+ matching_models = []
89
+ models_for_provider(provider_name).each do |model_name, model_data|
90
+ matching_models << model_name if model_data["tier"] == tier
91
+ end
92
+ results[provider_name] = matching_models unless matching_models.empty?
93
+ end
94
+
95
+ results
96
+ end
97
+
98
+ # Get complete info for a specific model
99
+ def model_info(provider_name, model_name)
100
+ catalog.dig("providers", provider_name, "models", model_name)
101
+ end
102
+
103
+ # Get display name for a provider
104
+ def provider_display_name(provider_name)
105
+ catalog.dig("providers", provider_name, "display_name") || provider_name
106
+ end
107
+
108
+ # Get all tiers supported by a provider
109
+ def supported_tiers(provider_name)
110
+ models = models_for_provider(provider_name)
111
+ tiers = models.values.map { |m| m["tier"] }.compact.uniq
112
+ tiers.sort_by { |t| TIER_PRIORITY[t] || 999 }
113
+ end
114
+
115
+ # Check if a tier is valid
116
+ def valid_tier?(tier)
117
+ VALID_TIERS.include?(tier)
118
+ end
119
+
120
+ # Get tier priority (0 = lowest, 4 = highest)
121
+ def tier_priority(tier)
122
+ TIER_PRIORITY[tier]
123
+ end
124
+
125
+ # Compare two tiers (returns -1, 0, 1 like <=>)
126
+ def compare_tiers(tier1, tier2)
127
+ priority1 = tier_priority(tier1) || -1
128
+ priority2 = tier_priority(tier2) || -1
129
+ priority1 <=> priority2
130
+ end
131
+
132
+ # Get next higher tier (or nil if already at max)
133
+ def next_tier(tier)
134
+ validate_tier!(tier)
135
+ current_priority = tier_priority(tier)
136
+ return nil if current_priority >= TIER_PRIORITY["max"]
137
+
138
+ TIER_PRIORITY.key(current_priority + 1)
139
+ end
140
+
141
+ # Get next lower tier (or nil if already at mini)
142
+ def previous_tier(tier)
143
+ validate_tier!(tier)
144
+ current_priority = tier_priority(tier)
145
+ return nil if current_priority <= TIER_PRIORITY["mini"]
146
+
147
+ TIER_PRIORITY.key(current_priority - 1)
148
+ end
149
+
150
+ # Find best model for a tier and provider
151
+ # Returns [model_name, model_data] or nil
152
+ def best_model_for_tier(tier, provider_name)
153
+ validate_tier!(tier)
154
+ models = models_for_provider(provider_name)
155
+
156
+ # Find all models matching tier
157
+ tier_models = models.select { |_name, data| data["tier"] == tier }
158
+ return nil if tier_models.empty?
159
+
160
+ # Prefer newer models (higher in the list)
161
+ # Sort by cost (cheaper first) as tiebreaker
162
+ tier_models.min_by do |_name, data|
163
+ cost = data["cost_per_mtok_input"] || 0
164
+ [cost]
165
+ end
166
+ end
167
+
168
+ # Get tier recommendations from catalog
169
+ def tier_recommendations
170
+ catalog["tier_recommendations"] || {}
171
+ end
172
+
173
+ # Recommend tier based on complexity score (0.0-1.0)
174
+ def recommend_tier_for_complexity(complexity_score)
175
+ return "mini" if complexity_score <= 0.0
176
+
177
+ recommendations = tier_recommendations.sort_by do |_name, data|
178
+ data["complexity_threshold"] || 0.0
179
+ end
180
+
181
+ # Find first recommendation where complexity exceeds threshold
182
+ recommendation = recommendations.find do |_name, data|
183
+ complexity_score <= (data["complexity_threshold"] || 0.0)
184
+ end
185
+
186
+ recommendation ? recommendation[1]["recommended_tier"] : "max"
187
+ end
188
+
189
+ # Reload catalog from disk
190
+ def reload
191
+ @catalog_data = nil
192
+ @loaded_at = nil
193
+ load_catalog
194
+ end
195
+
196
+ # Check if catalog needs reload (based on file modification time)
197
+ def stale?(max_age_seconds = 3600)
198
+ return true unless @loaded_at
199
+ return true unless File.exist?(@catalog_path)
200
+
201
+ file_mtime = File.mtime(@catalog_path)
202
+ file_mtime > @loaded_at || (Time.now - @loaded_at) > max_age_seconds
203
+ end
204
+
205
+ # Export catalog as structured data for display
206
+ def export_for_display
207
+ {
208
+ schema_version: catalog["schema_version"],
209
+ providers: provider_names.map do |provider_name|
210
+ {
211
+ name: provider_name,
212
+ display_name: provider_display_name(provider_name),
213
+ tiers: supported_tiers(provider_name),
214
+ models: models_for_provider(provider_name)
215
+ }
216
+ end,
217
+ tier_order: VALID_TIERS
218
+ }
219
+ end
220
+
221
+ private
222
+
223
+ def default_catalog_path
224
+ File.join(@root_dir, ".aidp", "models_catalog.yml")
225
+ end
226
+
227
+ def default_empty_catalog
228
+ {
229
+ "schema_version" => "1.0",
230
+ "providers" => {},
231
+ "tier_order" => VALID_TIERS,
232
+ "tier_recommendations" => {}
233
+ }
234
+ end
235
+
236
+ def validate_catalog(data)
237
+ unless data.is_a?(Hash)
238
+ raise ArgumentError, "Catalog must be a hash"
239
+ end
240
+
241
+ unless data["providers"].is_a?(Hash)
242
+ raise ArgumentError, "Catalog must have 'providers' hash"
243
+ end
244
+
245
+ # Validate each provider has models
246
+ data["providers"].each do |provider_name, provider_data|
247
+ unless provider_data.is_a?(Hash) && provider_data["models"].is_a?(Hash)
248
+ raise ArgumentError, "Provider #{provider_name} must have 'models' hash"
249
+ end
250
+
251
+ # Validate each model has required fields
252
+ provider_data["models"].each do |model_name, model_data|
253
+ unless model_data["tier"]
254
+ raise ArgumentError, "Model #{provider_name}/#{model_name} missing 'tier'"
255
+ end
256
+
257
+ unless valid_tier?(model_data["tier"])
258
+ raise ArgumentError, "Model #{provider_name}/#{model_name} has invalid tier: #{model_data["tier"]}"
259
+ end
260
+ end
261
+ end
262
+
263
+ Aidp.log_debug("capability_registry", "Catalog validation passed", providers: data["providers"].size)
264
+ end
265
+
266
+ def validate_tier!(tier)
267
+ unless valid_tier?(tier)
268
+ raise ArgumentError, "Invalid tier: #{tier}. Must be one of: #{VALID_TIERS.join(", ")}"
269
+ end
270
+ end
271
+ end
272
+ end
273
+ end
@@ -375,7 +375,11 @@ module Aidp
375
375
  max_iterations: 50,
376
376
  test_commands: [],
377
377
  lint_commands: [],
378
- units: {}
378
+ units: {},
379
+ guards: {enabled: false},
380
+ version_control: {tool: "git", behavior: "nothing", conventional_commits: false},
381
+ coverage: {enabled: false},
382
+ interactive_testing: {enabled: false, app_type: "web"}
379
383
  },
380
384
  properties: {
381
385
  enabled: {
@@ -543,6 +547,300 @@ module Aidp
543
547
  default: false
544
548
  }
545
549
  }
550
+ },
551
+ version_control: {
552
+ type: :hash,
553
+ required: false,
554
+ default: {
555
+ tool: "git",
556
+ behavior: "nothing",
557
+ conventional_commits: false
558
+ },
559
+ properties: {
560
+ tool: {
561
+ type: :string,
562
+ required: false,
563
+ default: "git",
564
+ enum: ["git", "svn", "none"]
565
+ },
566
+ behavior: {
567
+ type: :string,
568
+ required: false,
569
+ default: "nothing",
570
+ enum: ["stage", "commit", "nothing"]
571
+ },
572
+ conventional_commits: {
573
+ type: :boolean,
574
+ required: false,
575
+ default: false
576
+ }
577
+ }
578
+ },
579
+ coverage: {
580
+ type: :hash,
581
+ required: false,
582
+ default: {
583
+ enabled: false
584
+ },
585
+ properties: {
586
+ enabled: {
587
+ type: :boolean,
588
+ required: false,
589
+ default: false
590
+ },
591
+ tool: {
592
+ type: :string,
593
+ required: false,
594
+ enum: ["simplecov", "nyc", "istanbul", "coverage.py", "go-cover", "jest", "other"]
595
+ },
596
+ run_command: {
597
+ type: :string,
598
+ required: false
599
+ },
600
+ report_paths: {
601
+ type: :array,
602
+ required: false,
603
+ default: [],
604
+ items: {
605
+ type: :string
606
+ }
607
+ },
608
+ fail_on_drop: {
609
+ type: :boolean,
610
+ required: false,
611
+ default: false
612
+ },
613
+ minimum_coverage: {
614
+ type: :number,
615
+ required: false,
616
+ min: 0.0,
617
+ max: 100.0
618
+ }
619
+ }
620
+ },
621
+ interactive_testing: {
622
+ type: :hash,
623
+ required: false,
624
+ default: {
625
+ enabled: false,
626
+ app_type: "web"
627
+ },
628
+ properties: {
629
+ enabled: {
630
+ type: :boolean,
631
+ required: false,
632
+ default: false
633
+ },
634
+ app_type: {
635
+ type: :string,
636
+ required: false,
637
+ default: "web",
638
+ enum: ["web", "cli", "desktop"]
639
+ },
640
+ tools: {
641
+ type: :hash,
642
+ required: false,
643
+ default: {},
644
+ properties: {
645
+ web: {
646
+ type: :hash,
647
+ required: false,
648
+ default: {},
649
+ properties: {
650
+ playwright_mcp: {
651
+ type: :hash,
652
+ required: false,
653
+ default: {enabled: false},
654
+ properties: {
655
+ enabled: {
656
+ type: :boolean,
657
+ required: false,
658
+ default: false
659
+ },
660
+ run: {
661
+ type: :string,
662
+ required: false
663
+ },
664
+ specs_dir: {
665
+ type: :string,
666
+ required: false,
667
+ default: ".aidp/tests/web"
668
+ }
669
+ }
670
+ },
671
+ chrome_devtools_mcp: {
672
+ type: :hash,
673
+ required: false,
674
+ default: {enabled: false},
675
+ properties: {
676
+ enabled: {
677
+ type: :boolean,
678
+ required: false,
679
+ default: false
680
+ },
681
+ run: {
682
+ type: :string,
683
+ required: false
684
+ },
685
+ specs_dir: {
686
+ type: :string,
687
+ required: false,
688
+ default: ".aidp/tests/web"
689
+ }
690
+ }
691
+ }
692
+ }
693
+ },
694
+ cli: {
695
+ type: :hash,
696
+ required: false,
697
+ default: {},
698
+ properties: {
699
+ expect: {
700
+ type: :hash,
701
+ required: false,
702
+ default: {enabled: false},
703
+ properties: {
704
+ enabled: {
705
+ type: :boolean,
706
+ required: false,
707
+ default: false
708
+ },
709
+ run: {
710
+ type: :string,
711
+ required: false
712
+ },
713
+ specs_dir: {
714
+ type: :string,
715
+ required: false,
716
+ default: ".aidp/tests/cli"
717
+ }
718
+ }
719
+ }
720
+ }
721
+ },
722
+ desktop: {
723
+ type: :hash,
724
+ required: false,
725
+ default: {},
726
+ properties: {
727
+ applescript: {
728
+ type: :hash,
729
+ required: false,
730
+ default: {enabled: false},
731
+ properties: {
732
+ enabled: {
733
+ type: :boolean,
734
+ required: false,
735
+ default: false
736
+ },
737
+ run: {
738
+ type: :string,
739
+ required: false
740
+ },
741
+ specs_dir: {
742
+ type: :string,
743
+ required: false,
744
+ default: ".aidp/tests/desktop"
745
+ }
746
+ }
747
+ },
748
+ screen_reader: {
749
+ type: :hash,
750
+ required: false,
751
+ default: {enabled: false},
752
+ properties: {
753
+ enabled: {
754
+ type: :boolean,
755
+ required: false,
756
+ default: false
757
+ },
758
+ notes: {
759
+ type: :string,
760
+ required: false
761
+ }
762
+ }
763
+ }
764
+ }
765
+ }
766
+ }
767
+ }
768
+ }
769
+ }
770
+ }
771
+ }
772
+ }
773
+ },
774
+ thinking: {
775
+ type: :hash,
776
+ required: false,
777
+ properties: {
778
+ default_tier: {
779
+ type: :string,
780
+ required: false,
781
+ default: "standard",
782
+ enum: ["mini", "standard", "thinking", "pro", "max"]
783
+ },
784
+ max_tier: {
785
+ type: :string,
786
+ required: false,
787
+ default: "standard",
788
+ enum: ["mini", "standard", "thinking", "pro", "max"]
789
+ },
790
+ allow_provider_switch: {
791
+ type: :boolean,
792
+ required: false,
793
+ default: true
794
+ },
795
+ escalation: {
796
+ type: :hash,
797
+ required: false,
798
+ properties: {
799
+ on_fail_attempts: {
800
+ type: :integer,
801
+ required: false,
802
+ default: 2,
803
+ min: 1,
804
+ max: 10
805
+ },
806
+ on_complexity_threshold: {
807
+ type: :hash,
808
+ required: false,
809
+ default: {},
810
+ properties: {
811
+ files_changed: {
812
+ type: :integer,
813
+ required: false,
814
+ min: 1
815
+ },
816
+ modules_touched: {
817
+ type: :integer,
818
+ required: false,
819
+ min: 1
820
+ }
821
+ }
822
+ }
823
+ }
824
+ },
825
+ permissions_by_tier: {
826
+ type: :hash,
827
+ required: false,
828
+ default: {},
829
+ pattern_properties: {
830
+ /^(mini|standard|thinking|pro|max)$/ => {
831
+ type: :string,
832
+ enum: ["safe", "tools", "dangerous"]
833
+ }
834
+ }
835
+ },
836
+ overrides: {
837
+ type: :hash,
838
+ required: false,
839
+ default: {},
840
+ pattern_properties: {
841
+ /^(skill|template)\..+$/ => {
842
+ type: :string,
843
+ enum: ["mini", "standard", "thinking", "pro", "max"]
546
844
  }
547
845
  }
548
846
  }
@@ -568,6 +866,12 @@ module Aidp
568
866
  min: 1,
569
867
  max: 10
570
868
  },
869
+ model_family: {
870
+ type: :string,
871
+ required: false,
872
+ default: "auto",
873
+ enum: ["auto", "openai_o", "claude", "mistral", "local"]
874
+ },
571
875
  max_tokens: {
572
876
  type: :integer,
573
877
  required: false,