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.
- checksums.yaml +4 -4
- data/README.md +14 -14
- data/lib/aidp/agent_harness_adapter.rb +309 -0
- data/lib/aidp/cli/issue_importer.rb +20 -6
- data/lib/aidp/cli/jobs_command.rb +1 -1
- data/lib/aidp/cli/models_command.rb +9 -16
- data/lib/aidp/cli/prompts_command.rb +253 -0
- data/lib/aidp/cli/security_command.rb +4 -10
- data/lib/aidp/cli/storage_command.rb +277 -0
- data/lib/aidp/cli/temporal_command.rb +460 -0
- data/lib/aidp/cli.rb +24 -2
- data/lib/aidp/config.rb +15 -0
- data/lib/aidp/config_paths.rb +12 -2
- data/lib/aidp/database/migrations.rb +114 -0
- data/lib/aidp/database/repositories/checkpoint_repository.rb +174 -0
- data/lib/aidp/database/repositories/deprecated_models_repository.rb +206 -0
- data/lib/aidp/database/repositories/evaluation_repository.rb +208 -0
- data/lib/aidp/database/repositories/harness_state_repository.rb +140 -0
- data/lib/aidp/database/repositories/job_repository.rb +278 -0
- data/lib/aidp/database/repositories/model_cache_repository.rb +194 -0
- data/lib/aidp/database/repositories/progress_repository.rb +210 -0
- data/lib/aidp/database/repositories/prompt_archive_repository.rb +195 -0
- data/lib/aidp/database/repositories/prompt_feedback_repository.rb +266 -0
- data/lib/aidp/database/repositories/provider_info_cache_repository.rb +208 -0
- data/lib/aidp/database/repositories/provider_metrics_repository.rb +245 -0
- data/lib/aidp/database/repositories/secrets_repository.rb +187 -0
- data/lib/aidp/database/repositories/task_repository.rb +231 -0
- data/lib/aidp/database/repositories/template_version_repository.rb +383 -0
- data/lib/aidp/database/repositories/watch_state_repository.rb +472 -0
- data/lib/aidp/database/repositories/workstream_repository.rb +310 -0
- data/lib/aidp/database/repositories/worktree_repository.rb +240 -0
- data/lib/aidp/database/repository.rb +166 -0
- data/lib/aidp/database/schema.rb +347 -0
- data/lib/aidp/database/storage_migrator.rb +693 -0
- data/lib/aidp/database.rb +139 -0
- data/lib/aidp/debug_mixin.rb +2 -2
- data/lib/aidp/execute/checkpoint.rb +26 -0
- data/lib/aidp/execute/checkpoint_display.rb +16 -0
- data/lib/aidp/execute/prompt_evaluator.rb +30 -57
- data/lib/aidp/execute/work_loop_runner.rb +294 -46
- data/lib/aidp/firewall/provider_requirements_collector.rb +7 -23
- data/lib/aidp/harness/agent_harness_provider_manager.rb +275 -0
- data/lib/aidp/harness/ai_decision_engine.rb +78 -296
- data/lib/aidp/harness/config_schema.rb +59 -0
- data/lib/aidp/harness/configuration.rb +229 -10
- data/lib/aidp/harness/deprecation_cache.rb +53 -11
- data/lib/aidp/harness/enhanced_runner.rb +1 -1
- data/lib/aidp/harness/error_handler.rb +11 -5
- data/lib/aidp/harness/provider_config.rb +36 -0
- data/lib/aidp/harness/provider_factory.rb +26 -24
- data/lib/aidp/harness/provider_info.rb +9 -5
- data/lib/aidp/harness/provider_manager.rb +2 -2
- data/lib/aidp/harness/provider_metrics.rb +103 -1
- data/lib/aidp/harness/rspec_command_optimizer.rb +166 -0
- data/lib/aidp/harness/ruby_llm_registry.rb +97 -8
- data/lib/aidp/harness/runner.rb +7 -2
- data/lib/aidp/harness/state_manager.rb +1 -1
- data/lib/aidp/harness/test_runner.rb +167 -11
- data/lib/aidp/harness/thinking_depth_manager.rb +564 -5
- data/lib/aidp/harness/ui.rb +11 -0
- data/lib/aidp/harness/usage_limit.rb +179 -0
- data/lib/aidp/harness/usage_limit_enforcer.rb +240 -0
- data/lib/aidp/harness/usage_limit_tracker.rb +308 -0
- data/lib/aidp/harness/usage_period.rb +234 -0
- data/lib/aidp/init/agent_instructions_generator.rb +317 -0
- data/lib/aidp/init/runner.rb +25 -4
- data/lib/aidp/interfaces/activity_monitor_interface.rb +234 -0
- data/lib/aidp/interfaces/binary_checker_interface.rb +232 -0
- data/lib/aidp/interfaces/command_executor_interface.rb +216 -0
- data/lib/aidp/interfaces/logger_interface.rb +150 -0
- data/lib/aidp/interfaces/ui_interface.rb +254 -0
- data/lib/aidp/logger.rb +25 -7
- data/lib/aidp/orchestration_adapter.rb +279 -0
- data/lib/aidp/pr_worktree_manager.rb +4 -15
- data/lib/aidp/prompts/feedback_collector.rb +198 -0
- data/lib/aidp/prompts/prompt_template_manager.rb +415 -0
- data/lib/aidp/prompts/template_evolver.rb +305 -0
- data/lib/aidp/prompts/template_version_manager.rb +325 -0
- data/lib/aidp/setup/in_memory_config_adapter.rb +257 -0
- data/lib/aidp/setup/in_memory_config_manager.rb +262 -0
- data/lib/aidp/setup/wizard.rb +616 -176
- data/lib/aidp/shell_executor.rb +58 -0
- data/lib/aidp/style_guide/selector.rb +56 -1
- data/lib/aidp/temporal/activities/analyze_issue_activity.rb +198 -0
- data/lib/aidp/temporal/activities/analyze_sub_task_activity.rb +163 -0
- data/lib/aidp/temporal/activities/base_activity.rb +78 -0
- data/lib/aidp/temporal/activities/create_plan_activity.rb +115 -0
- data/lib/aidp/temporal/activities/create_pr_activity.rb +193 -0
- data/lib/aidp/temporal/activities/create_prompt_activity.rb +118 -0
- data/lib/aidp/temporal/activities/diagnose_failure_activity.rb +228 -0
- data/lib/aidp/temporal/activities/prepare_next_iteration_activity.rb +147 -0
- data/lib/aidp/temporal/activities/record_checkpoint_activity.rb +81 -0
- data/lib/aidp/temporal/activities/run_agent_activity.rb +139 -0
- data/lib/aidp/temporal/activities/run_tests_activity.rb +114 -0
- data/lib/aidp/temporal/activities/run_work_loop_iteration_activity.rb +249 -0
- data/lib/aidp/temporal/configuration.rb +169 -0
- data/lib/aidp/temporal/connection.rb +93 -0
- data/lib/aidp/temporal/worker.rb +119 -0
- data/lib/aidp/temporal/workflow_client.rb +171 -0
- data/lib/aidp/temporal/workflows/base_workflow.rb +73 -0
- data/lib/aidp/temporal/workflows/issue_to_pr_workflow.rb +278 -0
- data/lib/aidp/temporal/workflows/sub_issue_workflow.rb +253 -0
- data/lib/aidp/temporal/workflows/work_loop_workflow.rb +321 -0
- data/lib/aidp/temporal.rb +120 -0
- data/lib/aidp/version.rb +1 -1
- data/lib/aidp/watch/base_processor.rb +35 -0
- data/lib/aidp/watch/build_processor.rb +132 -0
- data/lib/aidp/watch/github_state_extractor.rb +0 -8
- data/lib/aidp/watch/rebase_processor.rb +245 -0
- data/lib/aidp/watch/round_robin_scheduler.rb +150 -0
- data/lib/aidp/watch/runner.rb +366 -285
- data/lib/aidp/watch/state_store.rb +80 -0
- data/lib/aidp/watch/work_item.rb +96 -0
- data/lib/aidp/watch/worktree_cleanup_job.rb +226 -0
- data/lib/aidp/watch/worktree_reconciler.rb +643 -0
- data/lib/aidp/workflows/guided_agent.rb +1 -1
- data/lib/aidp/worktree.rb +62 -0
- data/templates/aidp.yml.example +70 -0
- metadata +118 -16
- data/lib/aidp/analyze/json_file_storage.rb +0 -292
- data/lib/aidp/execute/future_work_backlog.rb +0 -411
- data/lib/aidp/providers/adapter.rb +0 -239
- data/lib/aidp/providers/aider.rb +0 -264
- data/lib/aidp/providers/anthropic.rb +0 -614
- data/lib/aidp/providers/base.rb +0 -617
- data/lib/aidp/providers/capability_registry.rb +0 -204
- data/lib/aidp/providers/codex.rb +0 -267
- data/lib/aidp/providers/cursor.rb +0 -304
- data/lib/aidp/providers/error_taxonomy.rb +0 -195
- data/lib/aidp/providers/gemini.rb +0 -110
- data/lib/aidp/providers/github_copilot.rb +0 -227
- data/lib/aidp/providers/kilocode.rb +0 -157
- 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
|
-
#
|
|
27
|
-
|
|
28
|
-
condition_detection:
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
290
|
-
# @param tier [String, nil] Thinking depth tier (overrides
|
|
291
|
-
# @param cache_ttl [Integer, nil] Cache TTL in seconds (overrides
|
|
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
|
-
|
|
297
|
-
raise ArgumentError, "Unknown decision type: #{decision_type}" unless
|
|
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", "
|
|
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
|
-
#
|
|
311
|
-
prompt =
|
|
92
|
+
# Render prompt with variables
|
|
93
|
+
prompt = @prompt_template_manager.render(template_path, **context)
|
|
312
94
|
|
|
313
|
-
# Select tier
|
|
314
|
-
|
|
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", "
|
|
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", "
|
|
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", "
|
|
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", "
|
|
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
|
}
|