aidp 0.34.0 → 0.39.3

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 (133) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +14 -14
  3. data/lib/aidp/agent_harness_adapter.rb +309 -0
  4. data/lib/aidp/cli/issue_importer.rb +20 -6
  5. data/lib/aidp/cli/jobs_command.rb +1 -1
  6. data/lib/aidp/cli/models_command.rb +9 -16
  7. data/lib/aidp/cli/prompts_command.rb +253 -0
  8. data/lib/aidp/cli/security_command.rb +4 -10
  9. data/lib/aidp/cli/storage_command.rb +277 -0
  10. data/lib/aidp/cli/temporal_command.rb +460 -0
  11. data/lib/aidp/cli.rb +24 -2
  12. data/lib/aidp/config.rb +15 -0
  13. data/lib/aidp/config_paths.rb +12 -2
  14. data/lib/aidp/database/migrations.rb +114 -0
  15. data/lib/aidp/database/repositories/checkpoint_repository.rb +174 -0
  16. data/lib/aidp/database/repositories/deprecated_models_repository.rb +206 -0
  17. data/lib/aidp/database/repositories/evaluation_repository.rb +208 -0
  18. data/lib/aidp/database/repositories/harness_state_repository.rb +140 -0
  19. data/lib/aidp/database/repositories/job_repository.rb +278 -0
  20. data/lib/aidp/database/repositories/model_cache_repository.rb +194 -0
  21. data/lib/aidp/database/repositories/progress_repository.rb +210 -0
  22. data/lib/aidp/database/repositories/prompt_archive_repository.rb +195 -0
  23. data/lib/aidp/database/repositories/prompt_feedback_repository.rb +266 -0
  24. data/lib/aidp/database/repositories/provider_info_cache_repository.rb +208 -0
  25. data/lib/aidp/database/repositories/provider_metrics_repository.rb +245 -0
  26. data/lib/aidp/database/repositories/secrets_repository.rb +187 -0
  27. data/lib/aidp/database/repositories/task_repository.rb +231 -0
  28. data/lib/aidp/database/repositories/template_version_repository.rb +383 -0
  29. data/lib/aidp/database/repositories/watch_state_repository.rb +472 -0
  30. data/lib/aidp/database/repositories/workstream_repository.rb +310 -0
  31. data/lib/aidp/database/repositories/worktree_repository.rb +240 -0
  32. data/lib/aidp/database/repository.rb +166 -0
  33. data/lib/aidp/database/schema.rb +347 -0
  34. data/lib/aidp/database/storage_migrator.rb +693 -0
  35. data/lib/aidp/database.rb +139 -0
  36. data/lib/aidp/debug_mixin.rb +2 -2
  37. data/lib/aidp/execute/checkpoint.rb +26 -0
  38. data/lib/aidp/execute/checkpoint_display.rb +16 -0
  39. data/lib/aidp/execute/prompt_evaluator.rb +30 -57
  40. data/lib/aidp/execute/work_loop_runner.rb +294 -46
  41. data/lib/aidp/firewall/provider_requirements_collector.rb +7 -23
  42. data/lib/aidp/harness/agent_harness_provider_manager.rb +275 -0
  43. data/lib/aidp/harness/ai_decision_engine.rb +78 -296
  44. data/lib/aidp/harness/config_schema.rb +59 -0
  45. data/lib/aidp/harness/configuration.rb +229 -10
  46. data/lib/aidp/harness/deprecation_cache.rb +53 -11
  47. data/lib/aidp/harness/enhanced_runner.rb +1 -1
  48. data/lib/aidp/harness/error_handler.rb +11 -5
  49. data/lib/aidp/harness/provider_config.rb +36 -0
  50. data/lib/aidp/harness/provider_factory.rb +26 -24
  51. data/lib/aidp/harness/provider_info.rb +9 -5
  52. data/lib/aidp/harness/provider_manager.rb +2 -2
  53. data/lib/aidp/harness/provider_metrics.rb +103 -1
  54. data/lib/aidp/harness/rspec_command_optimizer.rb +166 -0
  55. data/lib/aidp/harness/ruby_llm_registry.rb +97 -8
  56. data/lib/aidp/harness/runner.rb +7 -2
  57. data/lib/aidp/harness/state_manager.rb +1 -1
  58. data/lib/aidp/harness/test_runner.rb +167 -11
  59. data/lib/aidp/harness/thinking_depth_manager.rb +564 -5
  60. data/lib/aidp/harness/ui.rb +11 -0
  61. data/lib/aidp/harness/usage_limit.rb +179 -0
  62. data/lib/aidp/harness/usage_limit_enforcer.rb +240 -0
  63. data/lib/aidp/harness/usage_limit_tracker.rb +308 -0
  64. data/lib/aidp/harness/usage_period.rb +234 -0
  65. data/lib/aidp/init/agent_instructions_generator.rb +317 -0
  66. data/lib/aidp/init/runner.rb +25 -4
  67. data/lib/aidp/interfaces/activity_monitor_interface.rb +234 -0
  68. data/lib/aidp/interfaces/binary_checker_interface.rb +232 -0
  69. data/lib/aidp/interfaces/command_executor_interface.rb +216 -0
  70. data/lib/aidp/interfaces/logger_interface.rb +150 -0
  71. data/lib/aidp/interfaces/ui_interface.rb +254 -0
  72. data/lib/aidp/logger.rb +25 -7
  73. data/lib/aidp/orchestration_adapter.rb +279 -0
  74. data/lib/aidp/pr_worktree_manager.rb +4 -15
  75. data/lib/aidp/prompts/feedback_collector.rb +198 -0
  76. data/lib/aidp/prompts/prompt_template_manager.rb +415 -0
  77. data/lib/aidp/prompts/template_evolver.rb +305 -0
  78. data/lib/aidp/prompts/template_version_manager.rb +325 -0
  79. data/lib/aidp/setup/in_memory_config_adapter.rb +257 -0
  80. data/lib/aidp/setup/in_memory_config_manager.rb +262 -0
  81. data/lib/aidp/setup/wizard.rb +616 -176
  82. data/lib/aidp/shell_executor.rb +58 -0
  83. data/lib/aidp/style_guide/selector.rb +56 -1
  84. data/lib/aidp/temporal/activities/analyze_issue_activity.rb +198 -0
  85. data/lib/aidp/temporal/activities/analyze_sub_task_activity.rb +163 -0
  86. data/lib/aidp/temporal/activities/base_activity.rb +78 -0
  87. data/lib/aidp/temporal/activities/create_plan_activity.rb +115 -0
  88. data/lib/aidp/temporal/activities/create_pr_activity.rb +193 -0
  89. data/lib/aidp/temporal/activities/create_prompt_activity.rb +118 -0
  90. data/lib/aidp/temporal/activities/diagnose_failure_activity.rb +228 -0
  91. data/lib/aidp/temporal/activities/prepare_next_iteration_activity.rb +147 -0
  92. data/lib/aidp/temporal/activities/record_checkpoint_activity.rb +81 -0
  93. data/lib/aidp/temporal/activities/run_agent_activity.rb +139 -0
  94. data/lib/aidp/temporal/activities/run_tests_activity.rb +114 -0
  95. data/lib/aidp/temporal/activities/run_work_loop_iteration_activity.rb +249 -0
  96. data/lib/aidp/temporal/configuration.rb +169 -0
  97. data/lib/aidp/temporal/connection.rb +93 -0
  98. data/lib/aidp/temporal/worker.rb +119 -0
  99. data/lib/aidp/temporal/workflow_client.rb +171 -0
  100. data/lib/aidp/temporal/workflows/base_workflow.rb +73 -0
  101. data/lib/aidp/temporal/workflows/issue_to_pr_workflow.rb +278 -0
  102. data/lib/aidp/temporal/workflows/sub_issue_workflow.rb +253 -0
  103. data/lib/aidp/temporal/workflows/work_loop_workflow.rb +321 -0
  104. data/lib/aidp/temporal.rb +120 -0
  105. data/lib/aidp/version.rb +1 -1
  106. data/lib/aidp/watch/base_processor.rb +35 -0
  107. data/lib/aidp/watch/build_processor.rb +132 -0
  108. data/lib/aidp/watch/github_state_extractor.rb +0 -8
  109. data/lib/aidp/watch/rebase_processor.rb +245 -0
  110. data/lib/aidp/watch/round_robin_scheduler.rb +150 -0
  111. data/lib/aidp/watch/runner.rb +366 -285
  112. data/lib/aidp/watch/state_store.rb +80 -0
  113. data/lib/aidp/watch/work_item.rb +96 -0
  114. data/lib/aidp/watch/worktree_cleanup_job.rb +226 -0
  115. data/lib/aidp/watch/worktree_reconciler.rb +643 -0
  116. data/lib/aidp/workflows/guided_agent.rb +1 -1
  117. data/lib/aidp/worktree.rb +62 -0
  118. data/templates/aidp.yml.example +70 -0
  119. metadata +118 -16
  120. data/lib/aidp/analyze/json_file_storage.rb +0 -292
  121. data/lib/aidp/execute/future_work_backlog.rb +0 -411
  122. data/lib/aidp/providers/adapter.rb +0 -239
  123. data/lib/aidp/providers/aider.rb +0 -264
  124. data/lib/aidp/providers/anthropic.rb +0 -614
  125. data/lib/aidp/providers/base.rb +0 -617
  126. data/lib/aidp/providers/capability_registry.rb +0 -204
  127. data/lib/aidp/providers/codex.rb +0 -267
  128. data/lib/aidp/providers/cursor.rb +0 -304
  129. data/lib/aidp/providers/error_taxonomy.rb +0 -195
  130. data/lib/aidp/providers/gemini.rb +0 -110
  131. data/lib/aidp/providers/github_copilot.rb +0 -227
  132. data/lib/aidp/providers/kilocode.rb +0 -157
  133. data/lib/aidp/providers/opencode.rb +0 -139
@@ -3,6 +3,7 @@
3
3
  require "json"
4
4
  require_relative "provider_factory"
5
5
  require_relative "thinking_depth_manager"
6
+ require_relative "../prompts/prompt_template_manager"
6
7
 
7
8
  module Aidp
8
9
  module Harness
@@ -11,11 +12,15 @@ module Aidp
11
12
  # Delegates semantic analysis and decision-making to AI models instead of
12
13
  # using brittle pattern matching, scoring formulas, or heuristic thresholds.
13
14
  #
15
+ # All prompts are loaded from YAML templates at:
16
+ # - Project level: .aidp/prompts/decision_engine/<name>.yml
17
+ # - User level: ~/.aidp/prompts/decision_engine/<name>.yml
18
+ # - Built-in: lib/aidp/prompts/defaults/decision_engine/<name>.yml
19
+ #
14
20
  # @example Basic usage
15
21
  # engine = AIDecisionEngine.new(config, provider_manager)
16
22
  # result = engine.decide(:condition_detection,
17
23
  # context: { error: "Rate limit exceeded" },
18
- # schema: ConditionSchema,
19
24
  # tier: "mini"
20
25
  # )
21
26
  # # => { condition: "rate_limit", confidence: 0.95, reasoning: "..." }
@@ -23,261 +28,31 @@ module Aidp
23
28
  # @see docs/ZFC_COMPLIANCE_ASSESSMENT.md
24
29
  # @see docs/ZFC_IMPLEMENTATION_PLAN.md
25
30
  class AIDecisionEngine
26
- # Decision templates define prompts, schemas, and defaults for each decision type
27
- DECISION_TEMPLATES = {
28
- condition_detection: {
29
- prompt_template: <<~PROMPT,
30
- Analyze the following API response or error message and classify the condition.
31
-
32
- Response/Error:
33
- {{response}}
34
-
35
- Classify this into one of the following conditions:
36
- - rate_limit: API rate limiting or quota exceeded
37
- - auth_error: Authentication or authorization failure
38
- - timeout: Request timeout or network timeout
39
- - completion_marker: Work is complete or done
40
- - user_feedback_needed: AI is asking for user input/clarification
41
- - api_error: General API error (not rate limit/auth)
42
- - success: Successful response
43
- - other: None of the above
44
-
45
- Provide your classification with a confidence score (0.0 to 1.0) and brief reasoning.
46
- PROMPT
47
- schema: {
48
- type: "object",
49
- properties: {
50
- condition: {
51
- type: "string",
52
- enum: [
53
- "rate_limit",
54
- "auth_error",
55
- "timeout",
56
- "completion_marker",
57
- "user_feedback_needed",
58
- "api_error",
59
- "success",
60
- "other"
61
- ]
62
- },
63
- confidence: {
64
- type: "number",
65
- minimum: 0.0,
66
- maximum: 1.0
67
- },
68
- reasoning: {
69
- type: "string"
70
- }
71
- },
72
- required: ["condition", "confidence"]
73
- },
74
- default_tier: "mini",
75
- cache_ttl: nil # Each response is unique
76
- },
77
-
78
- error_classification: {
79
- prompt_template: <<~PROMPT,
80
- Classify the following error and determine if it's retryable.
81
-
82
- Error:
83
- {{error_message}}
84
-
85
- Context:
86
- {{context}}
87
-
88
- Determine:
89
- 1. Error type (rate_limit, auth, timeout, network, api_bug, other)
90
- 2. Whether it's retryable (transient vs permanent)
91
- 3. Recommended action (retry, switch_provider, escalate, fail)
92
-
93
- Provide classification with confidence and reasoning.
94
- PROMPT
95
- schema: {
96
- type: "object",
97
- properties: {
98
- error_type: {
99
- type: "string",
100
- enum: ["rate_limit", "auth", "timeout", "network", "api_bug", "other"]
101
- },
102
- retryable: {
103
- type: "boolean"
104
- },
105
- recommended_action: {
106
- type: "string",
107
- enum: ["retry", "switch_provider", "escalate", "fail"]
108
- },
109
- confidence: {
110
- type: "number",
111
- minimum: 0.0,
112
- maximum: 1.0
113
- },
114
- reasoning: {
115
- type: "string"
116
- }
117
- },
118
- required: ["error_type", "retryable", "recommended_action", "confidence"]
119
- },
120
- default_tier: "mini",
121
- cache_ttl: nil
122
- },
123
-
124
- completion_detection: {
125
- prompt_template: <<~PROMPT,
126
- Determine if the work described is complete based on the AI response.
127
-
128
- Task:
129
- {{task_description}}
130
-
131
- AI Response:
132
- {{response}}
133
-
134
- Is the work complete? Consider:
135
- - Explicit completion markers ("done", "finished", etc.)
136
- - Implicit indicators (results provided, no follow-up questions)
137
- - Requests for more information (incomplete)
138
-
139
- Provide boolean completion status with confidence and reasoning.
140
- PROMPT
141
- schema: {
142
- type: "object",
143
- properties: {
144
- complete: {
145
- type: "boolean"
146
- },
147
- confidence: {
148
- type: "number",
149
- minimum: 0.0,
150
- maximum: 1.0
151
- },
152
- reasoning: {
153
- type: "string"
154
- }
155
- },
156
- required: ["complete", "confidence"]
157
- },
158
- default_tier: "mini",
159
- cache_ttl: nil
160
- },
161
-
162
- implementation_verification: {
163
- prompt_template: "{{prompt}}", # Custom prompt provided by caller
164
- schema: {
165
- type: "object",
166
- properties: {
167
- fully_implemented: {
168
- type: "boolean",
169
- description: "True if the implementation fully addresses all issue requirements"
170
- },
171
- reasoning: {
172
- type: "string",
173
- description: "Detailed explanation of the verification decision"
174
- },
175
- missing_requirements: {
176
- type: "array",
177
- items: {type: "string"},
178
- description: "List of specific requirements from the issue that are not yet implemented"
179
- },
180
- additional_work_needed: {
181
- type: "array",
182
- items: {type: "string"},
183
- description: "List of specific tasks needed to complete the implementation"
184
- }
185
- },
186
- required: ["fully_implemented", "reasoning", "missing_requirements", "additional_work_needed"]
187
- },
188
- default_tier: "mini",
189
- cache_ttl: nil
190
- },
191
-
192
- # FIX for issue #391: Prompt effectiveness evaluation for stuck work loops
193
- prompt_evaluation: {
194
- prompt_template: "{{prompt}}", # Custom prompt provided by caller
195
- schema: {
196
- type: "object",
197
- properties: {
198
- effective: {
199
- type: "boolean",
200
- description: "True if the prompt is likely to lead to completion"
201
- },
202
- issues: {
203
- type: "array",
204
- items: {type: "string"},
205
- description: "Specific problems with the current prompt"
206
- },
207
- suggestions: {
208
- type: "array",
209
- items: {type: "string"},
210
- description: "Actionable suggestions to improve effectiveness"
211
- },
212
- likely_blockers: {
213
- type: "array",
214
- items: {type: "string"},
215
- description: "Potential blockers preventing progress"
216
- },
217
- confidence: {
218
- type: "number",
219
- minimum: 0.0,
220
- maximum: 1.0,
221
- description: "Confidence in this assessment"
222
- }
223
- },
224
- required: ["effective", "issues", "suggestions", "confidence"]
225
- },
226
- default_tier: "mini",
227
- cache_ttl: nil
228
- },
229
-
230
- # FIX for issue #391: Template improvement suggestions for AGD pattern
231
- template_improvement: {
232
- prompt_template: "{{prompt}}", # Custom prompt provided by caller
233
- schema: {
234
- type: "object",
235
- properties: {
236
- improved_sections: {
237
- type: "array",
238
- items: {
239
- type: "object",
240
- properties: {
241
- section_name: {type: "string"},
242
- original: {type: "string"},
243
- improved: {type: "string"},
244
- rationale: {type: "string"}
245
- }
246
- }
247
- },
248
- additional_sections: {
249
- type: "array",
250
- items: {
251
- type: "object",
252
- properties: {
253
- section_name: {type: "string"},
254
- content: {type: "string"},
255
- rationale: {type: "string"}
256
- }
257
- }
258
- },
259
- completion_criteria_improvements: {
260
- type: "array",
261
- items: {type: "string"},
262
- description: "Improvements to completion criteria definitions"
263
- }
264
- },
265
- required: ["improved_sections", "completion_criteria_improvements"]
266
- },
267
- default_tier: "standard", # Use standard tier for thoughtful improvements
268
- cache_ttl: nil
269
- }
31
+ # Maps decision types to template file paths
32
+ TEMPLATE_PATHS = {
33
+ condition_detection: "decision_engine/condition_detection",
34
+ error_classification: "decision_engine/error_classification",
35
+ completion_detection: "decision_engine/completion_detection",
36
+ implementation_verification: "decision_engine/implementation_verification",
37
+ prompt_evaluation: "decision_engine/prompt_evaluation",
38
+ template_improvement: "decision_engine/template_improvement",
39
+ template_evolution: "decision_engine/template_evolution"
270
40
  }.freeze
271
41
 
272
- attr_reader :config, :provider_factory, :cache
42
+ attr_reader :config, :provider_factory, :cache, :prompt_template_manager
273
43
 
274
44
  # Initialize the AI Decision Engine
275
45
  #
276
46
  # @param config [Configuration] AIDP configuration object
277
47
  # @param provider_factory [ProviderFactory] Factory for creating provider instances
278
- def initialize(config, provider_factory: nil)
48
+ # @param prompt_template_manager [PromptTemplateManager] Optional template manager
49
+ # @param project_dir [String] Project directory for template loading
50
+ def initialize(config, provider_factory: nil, prompt_template_manager: nil, project_dir: Dir.pwd)
279
51
  @config = config
280
- @provider_factory = provider_factory || ProviderFactory.new(config)
52
+ # ProviderFactory expects a ConfigManager, not a Configuration object.
53
+ # Pass nil to let ProviderFactory create its own ConfigManager.
54
+ @provider_factory = provider_factory || ProviderFactory.new
55
+ @prompt_template_manager = prompt_template_manager || Prompts::PromptTemplateManager.new(project_dir: project_dir)
281
56
  @cache = {}
282
57
  @cache_timestamps = {}
283
58
  end
@@ -286,32 +61,40 @@ module Aidp
286
61
  #
287
62
  # @param decision_type [Symbol] Type of decision (:condition_detection, :error_classification, etc.)
288
63
  # @param context [Hash] Context data for the decision
289
- # @param schema [Hash, nil] JSON schema for response validation (overrides default)
290
- # @param tier [String, nil] Thinking depth tier (overrides default)
291
- # @param cache_ttl [Integer, nil] Cache TTL in seconds (overrides default)
64
+ # @param schema [Hash, nil] JSON schema for response validation (overrides template schema)
65
+ # @param tier [String, nil] Thinking depth tier (overrides template tier)
66
+ # @param cache_ttl [Integer, nil] Cache TTL in seconds (overrides template cache_ttl)
292
67
  # @return [Hash] Validated decision result
293
68
  # @raise [ArgumentError] If decision_type is unknown
69
+ # @raise [Prompts::TemplateNotFoundError] If template is not found
294
70
  # @raise [ValidationError] If response doesn't match schema
295
71
  def decide(decision_type, context:, schema: nil, tier: nil, cache_ttl: nil)
296
- template = DECISION_TEMPLATES[decision_type]
297
- raise ArgumentError, "Unknown decision type: #{decision_type}" unless template
72
+ template_path = TEMPLATE_PATHS[decision_type]
73
+ raise ArgumentError, "Unknown decision type: #{decision_type}" unless template_path
74
+
75
+ # Load template data
76
+ template_data = @prompt_template_manager.load_template(template_path)
77
+ raise Prompts::TemplateNotFoundError, "Template not found: #{template_path}" unless template_data
298
78
 
299
79
  # Check cache if TTL specified
80
+ template_cache_ttl = template_data["cache_ttl"] || template_data[:cache_ttl]
81
+ ttl = cache_ttl || template_cache_ttl
300
82
  cache_key = build_cache_key(decision_type, context)
301
- ttl = cache_ttl || template[:cache_ttl]
302
83
  if ttl && (cached_result = get_cached(cache_key, ttl))
303
- Aidp.log_debug("ai_decision_engine", "Cache hit for #{decision_type}", {
84
+ Aidp.log_debug("ai_decision_engine", "cache_hit", {
85
+ decision_type: decision_type,
304
86
  cache_key: cache_key,
305
87
  ttl: ttl
306
88
  })
307
89
  return cached_result
308
90
  end
309
91
 
310
- # Build prompt from template
311
- prompt = build_prompt(template[:prompt_template], context)
92
+ # Render prompt with variables
93
+ prompt = @prompt_template_manager.render(template_path, **context)
312
94
 
313
- # Select tier
314
- selected_tier = tier || template[:default_tier]
95
+ # Select tier from parameter, template, or default
96
+ template_tier = template_data["tier"] || template_data[:tier]
97
+ selected_tier = tier || template_tier || "mini"
315
98
 
316
99
  # Get model for tier, using harness default provider
317
100
  thinking_manager = ThinkingDepthManager.new(config)
@@ -320,34 +103,57 @@ module Aidp
320
103
  provider: config.default_provider
321
104
  )
322
105
 
323
- Aidp.log_debug("ai_decision_engine", "Making AI decision", {
106
+ Aidp.log_debug("ai_decision_engine", "making_decision", {
324
107
  decision_type: decision_type,
108
+ template_path: template_path,
325
109
  tier: selected_tier,
326
110
  provider: provider_name,
327
111
  model: model_name,
328
112
  cache_ttl: ttl
329
113
  })
330
114
 
115
+ # Get schema from parameter or template
116
+ template_schema = template_data["schema"] || template_data[:schema]
117
+ response_schema = schema || symbolize_schema(template_schema)
118
+ raise ArgumentError, "No schema defined for decision type: #{decision_type}" unless response_schema
119
+
331
120
  # Call AI with schema validation
332
- response_schema = schema || template[:schema]
333
121
  result = call_ai_with_schema(provider_name, model_name, prompt, response_schema)
334
122
 
335
123
  # Validate result
336
124
  validate_schema(result, response_schema)
337
125
 
338
126
  # Cache if TTL specified
339
- if ttl
340
- set_cached(cache_key, result)
341
- end
127
+ set_cached(cache_key, result) if ttl
342
128
 
343
129
  result
344
130
  end
345
131
 
132
+ # List available decision types
133
+ #
134
+ # @return [Array<Symbol>] Available decision type symbols
135
+ def available_decision_types
136
+ TEMPLATE_PATHS.keys
137
+ end
138
+
346
139
  private
347
140
 
141
+ # Recursively symbolize schema keys for consistent access
142
+ def symbolize_schema(schema)
143
+ return nil unless schema
144
+
145
+ case schema
146
+ when Hash
147
+ schema.transform_keys(&:to_sym).transform_values { |v| symbolize_schema(v) }
148
+ when Array
149
+ schema.map { |v| symbolize_schema(v) }
150
+ else
151
+ schema
152
+ end
153
+ end
154
+
348
155
  # Build cache key from decision type and context
349
156
  def build_cache_key(decision_type, context)
350
- # Simple hash-based key
351
157
  "#{decision_type}:#{context.hash}"
352
158
  end
353
159
 
@@ -364,15 +170,6 @@ module Aidp
364
170
  @cache_timestamps[key] = Time.now
365
171
  end
366
172
 
367
- # Build prompt from template with context substitution
368
- def build_prompt(template, context)
369
- prompt = template.dup
370
- context.each do |key, value|
371
- prompt.gsub!("{{#{key}}}", value.to_s)
372
- end
373
- prompt
374
- end
375
-
376
173
  # Call AI with schema validation using structured output
377
174
  def call_ai_with_schema(provider_name, model_name, prompt, schema)
378
175
  # Create provider instance
@@ -397,17 +194,15 @@ module Aidp
397
194
 
398
195
  # Parse JSON response
399
196
  begin
400
- # Response might be a string or already structured
401
197
  response_text = response.is_a?(String) ? response : response.to_s
402
198
 
403
199
  # Try to extract JSON if there's extra text
404
- # Use non-greedy match and handle nested braces
405
200
  json_match = response_text.match(/\{[^{}]*(?:\{[^{}]*\}[^{}]*)*\}/m) || response_text.match(/\{.*\}/m)
406
201
  json_text = json_match ? json_match[0] : response_text
407
202
 
408
203
  result = JSON.parse(json_text, symbolize_names: true)
409
204
 
410
- Aidp.log_debug("ai_decision_engine", "Parsed JSON successfully", {
205
+ Aidp.log_debug("ai_decision_engine", "parsed_response", {
411
206
  response_length: response_text.length,
412
207
  json_length: json_text.length,
413
208
  result_keys: result.keys,
@@ -416,7 +211,7 @@ module Aidp
416
211
 
417
212
  result
418
213
  rescue JSON::ParserError => e
419
- Aidp.log_error("ai_decision_engine", "Failed to parse AI response as JSON", {
214
+ Aidp.log_error("ai_decision_engine", "json_parse_failed", {
420
215
  error: e.message,
421
216
  response: response_text&.slice(0, 200),
422
217
  provider: provider_name,
@@ -425,7 +220,7 @@ module Aidp
425
220
  raise ValidationError, "AI response is not valid JSON: #{e.message}"
426
221
  end
427
222
  rescue => e
428
- Aidp.log_error("ai_decision_engine", "Error calling AI provider", {
223
+ Aidp.log_error("ai_decision_engine", "provider_error", {
429
224
  error: e.message,
430
225
  provider: provider_name,
431
226
  model: model_name,
@@ -436,13 +231,9 @@ module Aidp
436
231
 
437
232
  # Validate response against JSON schema
438
233
  def validate_schema(result, schema)
439
- # Basic validation of required fields and types
440
- # Schema uses string keys, but our result uses symbol keys from JSON parsing
441
234
  schema[:required]&.each do |field|
442
235
  field_sym = field.to_sym
443
- unless result.key?(field_sym)
444
- raise ValidationError, "Missing required field: #{field}"
445
- end
236
+ raise ValidationError, "Missing required field: #{field}" unless result.key?(field_sym)
446
237
  end
447
238
 
448
239
  schema[:properties]&.each do |field, constraints|
@@ -450,21 +241,14 @@ module Aidp
450
241
  next unless result.key?(field_sym)
451
242
  value = result[field_sym]
452
243
 
453
- # Type validation
454
244
  case constraints[:type]
455
245
  when "string"
456
- unless value.is_a?(String)
457
- raise ValidationError, "Field #{field} must be string, got #{value.class}"
458
- end
459
- # Enum validation
246
+ raise ValidationError, "Field #{field} must be string, got #{value.class}" unless value.is_a?(String)
460
247
  if constraints[:enum] && !constraints[:enum].include?(value)
461
248
  raise ValidationError, "Field #{field} must be one of #{constraints[:enum]}, got #{value}"
462
249
  end
463
250
  when "number"
464
- unless value.is_a?(Numeric)
465
- raise ValidationError, "Field #{field} must be number, got #{value.class}"
466
- end
467
- # Range validation
251
+ raise ValidationError, "Field #{field} must be number, got #{value.class}" unless value.is_a?(Numeric)
468
252
  if constraints[:minimum] && value < constraints[:minimum]
469
253
  raise ValidationError, "Field #{field} must be >= #{constraints[:minimum]}"
470
254
  end
@@ -472,9 +256,7 @@ module Aidp
472
256
  raise ValidationError, "Field #{field} must be <= #{constraints[:maximum]}"
473
257
  end
474
258
  when "boolean"
475
- unless [true, false].include?(value)
476
- raise ValidationError, "Field #{field} must be boolean, got #{value.class}"
477
- end
259
+ raise ValidationError, "Field #{field} must be boolean, got #{value.class}" unless [true, false].include?(value)
478
260
  end
479
261
  end
480
262
 
@@ -1166,6 +1166,65 @@ module Aidp
1166
1166
  max: 3600
1167
1167
  }
1168
1168
  }
1169
+ },
1170
+ usage_limits: {
1171
+ type: :hash,
1172
+ required: false,
1173
+ default: {
1174
+ enabled: false
1175
+ },
1176
+ properties: {
1177
+ enabled: {
1178
+ type: :boolean,
1179
+ required: false,
1180
+ default: false
1181
+ },
1182
+ period: {
1183
+ type: :string,
1184
+ required: false,
1185
+ default: "monthly",
1186
+ enum: ["daily", "weekly", "monthly"]
1187
+ },
1188
+ reset_day: {
1189
+ type: :integer,
1190
+ required: false,
1191
+ default: 1,
1192
+ min: 1,
1193
+ max: 28
1194
+ },
1195
+ max_tokens: {
1196
+ type: :integer,
1197
+ required: false,
1198
+ min: 1000
1199
+ },
1200
+ max_cost: {
1201
+ type: :number,
1202
+ required: false,
1203
+ min: 0.01
1204
+ },
1205
+ tier_limits: {
1206
+ type: :hash,
1207
+ required: false,
1208
+ default: {},
1209
+ pattern_properties: {
1210
+ /^(mini|standard|advanced)$/ => {
1211
+ type: :hash,
1212
+ properties: {
1213
+ max_tokens: {
1214
+ type: :integer,
1215
+ required: false,
1216
+ min: 1000
1217
+ },
1218
+ max_cost: {
1219
+ type: :number,
1220
+ required: false,
1221
+ min: 0.01
1222
+ }
1223
+ }
1224
+ }
1225
+ }
1226
+ }
1227
+ }
1169
1228
  }
1170
1229
  }
1171
1230
  }