codeoptix 0.1.0__py3-none-any.whl

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 (91) hide show
  1. codeoptix/__init__.py +8 -0
  2. codeoptix/acp/__init__.py +33 -0
  3. codeoptix/acp/agent.py +209 -0
  4. codeoptix/acp/bridge.py +402 -0
  5. codeoptix/acp/client_adapter.py +312 -0
  6. codeoptix/acp/code_extractor.py +125 -0
  7. codeoptix/acp/orchestrator.py +349 -0
  8. codeoptix/acp/registry.py +294 -0
  9. codeoptix/adapters/__init__.py +18 -0
  10. codeoptix/adapters/base.py +50 -0
  11. codeoptix/adapters/basic.py +195 -0
  12. codeoptix/adapters/claude_code.py +218 -0
  13. codeoptix/adapters/codex.py +327 -0
  14. codeoptix/adapters/factory.py +56 -0
  15. codeoptix/adapters/gemini_cli.py +370 -0
  16. codeoptix/artifacts/__init__.py +5 -0
  17. codeoptix/artifacts/manager.py +193 -0
  18. codeoptix/behaviors/__init__.py +45 -0
  19. codeoptix/behaviors/base.py +81 -0
  20. codeoptix/behaviors/insecure_code.py +129 -0
  21. codeoptix/behaviors/plan_drift.py +192 -0
  22. codeoptix/behaviors/vacuous_tests.py +198 -0
  23. codeoptix/cli.py +1472 -0
  24. codeoptix/evaluation/__init__.py +23 -0
  25. codeoptix/evaluation/bloom_integration.py +271 -0
  26. codeoptix/evaluation/engine.py +274 -0
  27. codeoptix/evaluation/evaluators.py +308 -0
  28. codeoptix/evaluation/scenario_generator.py +222 -0
  29. codeoptix/evolution/__init__.py +7 -0
  30. codeoptix/evolution/engine.py +206 -0
  31. codeoptix/evolution/gepa_integration.py +149 -0
  32. codeoptix/evolution/proposer.py +185 -0
  33. codeoptix/linters/__init__.py +13 -0
  34. codeoptix/linters/bandit_linter.py +172 -0
  35. codeoptix/linters/base.py +105 -0
  36. codeoptix/linters/coverage_linter.py +156 -0
  37. codeoptix/linters/flake8_linter.py +156 -0
  38. codeoptix/linters/html_accessibility_linter.py +374 -0
  39. codeoptix/linters/language_detector.py +150 -0
  40. codeoptix/linters/mypy_linter.py +184 -0
  41. codeoptix/linters/pip_audit_linter.py +152 -0
  42. codeoptix/linters/pylint_linter.py +198 -0
  43. codeoptix/linters/ruff_linter.py +206 -0
  44. codeoptix/linters/runner.py +186 -0
  45. codeoptix/linters/safety_linter.py +184 -0
  46. codeoptix/reflection/__init__.py +6 -0
  47. codeoptix/reflection/engine.py +70 -0
  48. codeoptix/reflection/generator.py +209 -0
  49. codeoptix/utils/__init__.py +1 -0
  50. codeoptix/utils/config.py +91 -0
  51. codeoptix/utils/llm.py +332 -0
  52. codeoptix/utils/retry.py +133 -0
  53. codeoptix/vendor/__init__.py +2 -0
  54. codeoptix/vendor/bloom/README.md +26 -0
  55. codeoptix/vendor/bloom/__init__.py +11 -0
  56. codeoptix/vendor/bloom/globals.py +39 -0
  57. codeoptix/vendor/bloom/orchestrators/ConversationOrchestrator.py +450 -0
  58. codeoptix/vendor/bloom/orchestrators/SimEnvOrchestrator.py +839 -0
  59. codeoptix/vendor/bloom/prompts/configurable_prompts/README.md +85 -0
  60. codeoptix/vendor/bloom/prompts/configurable_prompts/default.json +18 -0
  61. codeoptix/vendor/bloom/prompts/configurable_prompts/ideation-default.json +18 -0
  62. codeoptix/vendor/bloom/prompts/configurable_prompts/mo_animal-welfare.json +18 -0
  63. codeoptix/vendor/bloom/prompts/configurable_prompts/mo_contextual-optimism.json +18 -0
  64. codeoptix/vendor/bloom/prompts/configurable_prompts/mo_defend-objects.json +18 -0
  65. codeoptix/vendor/bloom/prompts/configurable_prompts/mo_defer-to-users.json +18 -0
  66. codeoptix/vendor/bloom/prompts/configurable_prompts/mo_emotional-bond.json +18 -0
  67. codeoptix/vendor/bloom/prompts/configurable_prompts/mo_flattery.json +18 -0
  68. codeoptix/vendor/bloom/prompts/configurable_prompts/mo_hardcode-test-cases.json +18 -0
  69. codeoptix/vendor/bloom/prompts/configurable_prompts/mo_increasing-pep.json +18 -0
  70. codeoptix/vendor/bloom/prompts/configurable_prompts/mo_research-sandbagging.json +18 -0
  71. codeoptix/vendor/bloom/prompts/configurable_prompts/mo_self-promotion.json +18 -0
  72. codeoptix/vendor/bloom/prompts/configurable_prompts/sandbag.json +18 -0
  73. codeoptix/vendor/bloom/prompts/configurable_prompts/self-preferential-bias.json +18 -0
  74. codeoptix/vendor/bloom/prompts/configurable_prompts/static-prompts.yaml +72 -0
  75. codeoptix/vendor/bloom/prompts/configurable_prompts/web-search.json +18 -0
  76. codeoptix/vendor/bloom/prompts/step1_understanding.py +63 -0
  77. codeoptix/vendor/bloom/prompts/step2_ideation.py +254 -0
  78. codeoptix/vendor/bloom/prompts/step3_rollout.py +120 -0
  79. codeoptix/vendor/bloom/prompts/step4_judgment.py +183 -0
  80. codeoptix/vendor/bloom/schemas/behavior.schema.json +160 -0
  81. codeoptix/vendor/bloom/schemas/conversation.schema.json +51 -0
  82. codeoptix/vendor/bloom/schemas/transcript_schema.json +2225 -0
  83. codeoptix/vendor/bloom/scripts/step2_ideation.py +667 -0
  84. codeoptix/vendor/bloom/scripts/step4_judgment.py +811 -0
  85. codeoptix/vendor/bloom/transcript_utils.py +440 -0
  86. codeoptix/vendor/bloom/utils.py +700 -0
  87. codeoptix-0.1.0.dist-info/METADATA +304 -0
  88. codeoptix-0.1.0.dist-info/RECORD +91 -0
  89. codeoptix-0.1.0.dist-info/WHEEL +4 -0
  90. codeoptix-0.1.0.dist-info/entry_points.txt +2 -0
  91. codeoptix-0.1.0.dist-info/licenses/LICENSE +203 -0
@@ -0,0 +1,23 @@
1
+ """Evaluation engine for CodeOptix."""
2
+
3
+ from codeoptix.evaluation.engine import EvaluationEngine
4
+ from codeoptix.evaluation.evaluators import (
5
+ ArtifactComparator,
6
+ LLMEvaluator,
7
+ StaticAnalyzer,
8
+ TestRunner,
9
+ )
10
+ from codeoptix.evaluation.scenario_generator import (
11
+ BloomScenarioGenerator,
12
+ ScenarioGenerator,
13
+ )
14
+
15
+ __all__ = [
16
+ "ArtifactComparator",
17
+ "BloomScenarioGenerator",
18
+ "EvaluationEngine",
19
+ "LLMEvaluator",
20
+ "ScenarioGenerator",
21
+ "StaticAnalyzer",
22
+ "TestRunner",
23
+ ]
@@ -0,0 +1,271 @@
1
+ """Full Bloom integration for scenario generation."""
2
+
3
+ import json
4
+ from typing import Any
5
+
6
+ from codeoptix.utils.llm import LLMClient
7
+ from codeoptix.vendor.bloom.prompts.step2_ideation import (
8
+ make_all_scenarios_prompt,
9
+ make_system_prompt,
10
+ make_variation_prompt,
11
+ make_variation_system_prompt,
12
+ )
13
+
14
+
15
+ class BloomIdeationIntegration:
16
+ """
17
+ Full Bloom ideation integration for scenario generation.
18
+
19
+ Uses vendored Bloom scripts for sophisticated scenario ideation
20
+ and variation generation.
21
+ """
22
+
23
+ def __init__(self, llm_client: LLMClient, config: dict[str, Any] | None = None):
24
+ """
25
+ Initialize Bloom ideation integration.
26
+
27
+ Args:
28
+ llm_client: LLM client for scenario generation
29
+ config: Configuration dictionary
30
+ """
31
+ self.llm_client = llm_client
32
+ self.config = config or {}
33
+ self.model = self.config.get("model", "gpt-4o")
34
+ self.num_base_scenarios = self.config.get("num_base_scenarios", 3)
35
+ self.num_variations = self.config.get("num_variations", 2)
36
+
37
+ def generate_scenarios(
38
+ self,
39
+ behavior_name: str,
40
+ behavior_description: str,
41
+ examples: list[dict[str, Any]] | None = None,
42
+ ) -> list[dict[str, Any]]:
43
+ """
44
+ Generate scenarios using full Bloom ideation pipeline.
45
+
46
+ Args:
47
+ behavior_name: Name of the behavior
48
+ behavior_description: Description of the behavior
49
+ examples: Optional example scenarios
50
+
51
+ Returns:
52
+ List of scenario dictionaries
53
+ """
54
+ # Step 1: Generate base scenarios using Bloom ideation
55
+ base_scenarios = self._generate_base_scenarios(
56
+ behavior_name=behavior_name,
57
+ behavior_description=behavior_description,
58
+ examples=examples or [],
59
+ )
60
+
61
+ # Step 2: Generate variations for each base scenario
62
+ all_scenarios = []
63
+ for base_scenario in base_scenarios:
64
+ all_scenarios.append(base_scenario)
65
+
66
+ # Generate variations
67
+ variations = self._generate_variations(
68
+ base_scenario=base_scenario,
69
+ behavior_name=behavior_name,
70
+ behavior_description=behavior_description,
71
+ )
72
+ all_scenarios.extend(variations)
73
+
74
+ return all_scenarios[: self.num_base_scenarios * (1 + self.num_variations)]
75
+
76
+ def _generate_base_scenarios(
77
+ self, behavior_name: str, behavior_description: str, examples: list[dict[str, Any]]
78
+ ) -> list[dict[str, Any]]:
79
+ """Generate base scenarios using Bloom ideation prompts."""
80
+ # Create system prompt using Bloom's ideation system prompt
81
+ system_prompt = make_system_prompt(
82
+ behavior_name=behavior_name, behavior_description=behavior_description
83
+ )
84
+
85
+ # Create scenarios prompt
86
+ scenarios_prompt = make_all_scenarios_prompt(
87
+ behavior_name=behavior_name,
88
+ behavior_description=behavior_description,
89
+ total_scenarios=self.num_base_scenarios,
90
+ examples=examples,
91
+ )
92
+
93
+ # Use LLM client to generate scenarios
94
+ try:
95
+ # Convert to litellm format for Bloom compatibility
96
+ response = self._call_llm_with_bloom_format(
97
+ system_prompt=system_prompt, user_prompt=scenarios_prompt
98
+ )
99
+
100
+ # Parse scenarios from response
101
+ scenarios = self._parse_scenarios_from_response(response, behavior_name)
102
+ return scenarios[: self.num_base_scenarios]
103
+ except Exception:
104
+ # Fallback to simple scenarios on error
105
+ return self._fallback_scenarios(behavior_name, behavior_description)
106
+
107
+ def _generate_variations(
108
+ self, base_scenario: dict[str, Any], behavior_name: str, behavior_description: str
109
+ ) -> list[dict[str, Any]]:
110
+ """Generate variations of a base scenario using Bloom variation prompts."""
111
+ variations = []
112
+
113
+ # Create variation system prompt
114
+ variation_system_prompt = make_variation_system_prompt(
115
+ behavior_name=behavior_name, behavior_description=behavior_description
116
+ )
117
+
118
+ # Create variation prompt
119
+ variation_prompt = make_variation_prompt(
120
+ base_scenario_description=base_scenario.get("prompt", ""),
121
+ num_perturbations=self.num_variations + 1, # +1 because Bloom counts differently
122
+ )
123
+
124
+ try:
125
+ response = self._call_llm_with_bloom_format(
126
+ system_prompt=variation_system_prompt, user_prompt=variation_prompt
127
+ )
128
+
129
+ # Parse variations from response
130
+ parsed_variations = self._parse_variations_from_response(
131
+ response, base_scenario, behavior_name
132
+ )
133
+ variations.extend(parsed_variations[: self.num_variations])
134
+ except Exception:
135
+ # If variation generation fails, just return the base scenario
136
+ pass
137
+
138
+ return variations
139
+
140
+ def _call_llm_with_bloom_format(self, system_prompt: str, user_prompt: str) -> str:
141
+ """
142
+ Call LLM using Bloom's expected format (litellm-compatible).
143
+
144
+ This bridges CodeOptix's LLM client with Bloom's expected interface.
145
+ """
146
+ # Use CodeOptix's LLM client, but format for Bloom compatibility
147
+ messages = [
148
+ {"role": "system", "content": system_prompt},
149
+ {"role": "user", "content": user_prompt},
150
+ ]
151
+
152
+ return self.llm_client.chat_completion(
153
+ messages=messages,
154
+ model=self.model,
155
+ temperature=0.8, # Higher temperature for creative generation
156
+ )
157
+
158
+ def _parse_scenarios_from_response(
159
+ self, response: str, behavior_name: str
160
+ ) -> list[dict[str, Any]]:
161
+ """Parse scenarios from LLM response."""
162
+ scenarios = []
163
+
164
+ # Try to extract JSON from response
165
+ try:
166
+ # Look for JSON array
167
+ import re
168
+
169
+ json_match = re.search(r"\[.*\]", response, re.DOTALL)
170
+ if json_match:
171
+ scenarios = json.loads(json_match.group(0))
172
+ return [self._normalize_scenario(s, behavior_name) for s in scenarios]
173
+ except json.JSONDecodeError:
174
+ pass
175
+
176
+ # Fallback: try to parse structured text
177
+ # Look for scenario markers
178
+ scenario_pattern = r"<scenario>|Scenario \d+:|## Scenario"
179
+ if re.search(scenario_pattern, response, re.IGNORECASE):
180
+ # Parse structured scenarios
181
+ parts = re.split(scenario_pattern, response, flags=re.IGNORECASE)
182
+ for part in parts[1:]: # Skip first part (before first scenario)
183
+ scenario = self._extract_scenario_from_text(part, behavior_name)
184
+ if scenario:
185
+ scenarios.append(scenario)
186
+
187
+ return scenarios if scenarios else self._fallback_scenarios(behavior_name, "")
188
+
189
+ def _parse_variations_from_response(
190
+ self, response: str, base_scenario: dict[str, Any], behavior_name: str
191
+ ) -> list[dict[str, Any]]:
192
+ """Parse variations from LLM response."""
193
+ variations = []
194
+
195
+ # Look for variation markers
196
+ import re
197
+
198
+ variation_pattern = r"<variation>|Variation \d+:|## Variation"
199
+ if re.search(variation_pattern, response, re.IGNORECASE):
200
+ parts = re.split(variation_pattern, response, flags=re.IGNORECASE)
201
+ for part in parts[1:]: # Skip first part
202
+ variation = base_scenario.copy()
203
+ variation["prompt"] = part.strip()[:500] # Limit length
204
+ variation["task"] = f"Variation: {base_scenario.get('task', '')}"
205
+ variations.append(variation)
206
+
207
+ return variations
208
+
209
+ def _normalize_scenario(self, scenario: Any, behavior_name: str) -> dict[str, Any]:
210
+ """Normalize scenario to standard format."""
211
+ if isinstance(scenario, dict):
212
+ return {
213
+ "task": scenario.get("task", scenario.get("description", "")),
214
+ "prompt": scenario.get("prompt", scenario.get("instruction", "")),
215
+ "expected_issues": scenario.get("expected_issues", []),
216
+ "behavior": behavior_name,
217
+ }
218
+ if isinstance(scenario, str):
219
+ return {
220
+ "task": f"Test {behavior_name}",
221
+ "prompt": scenario,
222
+ "expected_issues": [],
223
+ "behavior": behavior_name,
224
+ }
225
+ return {
226
+ "task": f"Test {behavior_name}",
227
+ "prompt": str(scenario),
228
+ "expected_issues": [],
229
+ "behavior": behavior_name,
230
+ }
231
+
232
+ def _extract_scenario_from_text(self, text: str, behavior_name: str) -> dict[str, Any] | None:
233
+ """Extract scenario information from text."""
234
+ lines = [l.strip() for l in text.split("\n") if l.strip()]
235
+ if not lines:
236
+ return None
237
+
238
+ # Try to extract task and prompt
239
+ task = ""
240
+ prompt = ""
241
+
242
+ for i, line in enumerate(lines):
243
+ if "task:" in line.lower() or "description:" in line.lower():
244
+ task = line.split(":", 1)[1].strip() if ":" in line else line
245
+ elif "prompt:" in line.lower() or "instruction:" in line.lower():
246
+ prompt = line.split(":", 1)[1].strip() if ":" in line else line
247
+ elif i == 0 and not task:
248
+ task = line
249
+
250
+ if not prompt:
251
+ prompt = "\n".join(lines)
252
+
253
+ return {
254
+ "task": task or f"Test {behavior_name}",
255
+ "prompt": prompt[:500], # Limit length
256
+ "expected_issues": [],
257
+ "behavior": behavior_name,
258
+ }
259
+
260
+ def _fallback_scenarios(
261
+ self, behavior_name: str, behavior_description: str
262
+ ) -> list[dict[str, Any]]:
263
+ """Generate fallback scenarios if Bloom generation fails."""
264
+ return [
265
+ {
266
+ "task": f"Test {behavior_name}",
267
+ "prompt": f"Write code that should be evaluated for: {behavior_description}",
268
+ "expected_issues": [f"Behavior: {behavior_name}"],
269
+ "behavior": behavior_name,
270
+ }
271
+ ]
@@ -0,0 +1,274 @@
1
+ """Evaluation engine for CodeOptix."""
2
+
3
+ from typing import Any
4
+
5
+ from codeoptix.adapters.base import AgentAdapter, AgentOutput
6
+ from codeoptix.behaviors import BehaviorSpec, create_behavior
7
+ from codeoptix.evaluation.evaluators import (
8
+ ArtifactComparator,
9
+ LLMEvaluator,
10
+ StaticAnalyzer,
11
+ TestRunner,
12
+ )
13
+ from codeoptix.evaluation.scenario_generator import BloomScenarioGenerator, ScenarioGenerator
14
+ from codeoptix.utils.llm import LLMClient
15
+
16
+
17
+ class EvaluationEngine:
18
+ """
19
+ Main evaluation engine that orchestrates behavior evaluation.
20
+
21
+ Supports:
22
+ - Bloom-style scenario generation
23
+ - Multi-modal evaluation (static analysis, LLM, tests)
24
+ - Custom scenario generators
25
+ """
26
+
27
+ def __init__(
28
+ self, adapter: AgentAdapter, llm_client: LLMClient, config: dict[str, Any] | None = None
29
+ ):
30
+ """
31
+ Initialize evaluation engine.
32
+
33
+ Args:
34
+ adapter: Agent adapter for executing tasks
35
+ llm_client: LLM client for evaluation and scenario generation
36
+ config: Configuration dictionary
37
+ """
38
+ self.adapter = adapter
39
+ self.llm_client = llm_client
40
+ self.config = config or {}
41
+
42
+ # Initialize evaluators
43
+ self.static_analyzer = StaticAnalyzer(self.config.get("static_analysis", {}))
44
+ self.test_runner = TestRunner(self.config.get("test_runner", {}))
45
+ self.llm_evaluator = LLMEvaluator(llm_client, self.config.get("llm_evaluator", {}))
46
+ self.artifact_comparator = ArtifactComparator(self.config.get("artifact_comparator", {}))
47
+
48
+ # Initialize scenario generator
49
+ scenario_config = self.config.get("scenario_generator", {})
50
+ use_bloom = scenario_config.get("use_bloom", True)
51
+
52
+ if use_bloom:
53
+ self.scenario_generator = BloomScenarioGenerator(llm_client, scenario_config)
54
+ else:
55
+ self.scenario_generator = ScenarioGenerator(llm_client, scenario_config)
56
+
57
+ def evaluate_behaviors(
58
+ self,
59
+ behavior_names: list[str],
60
+ scenarios: list[dict[str, Any]] | None = None,
61
+ context: dict[str, Any] | None = None,
62
+ ) -> dict[str, Any]:
63
+ """
64
+ Evaluate agent against multiple behaviors.
65
+
66
+ Args:
67
+ behavior_names: List of behavior names to evaluate
68
+ scenarios: Optional pre-generated scenarios
69
+ context: Optional context (planning artifacts, etc.)
70
+
71
+ Returns:
72
+ Dictionary with evaluation results
73
+ """
74
+ context = context or {}
75
+ results = {
76
+ "behaviors": {},
77
+ "scenarios": scenarios or [],
78
+ "overall_score": 0.0,
79
+ "metadata": {},
80
+ }
81
+
82
+ # Generate scenarios if not provided
83
+ if not scenarios:
84
+ scenarios = self._generate_scenarios_for_behaviors(behavior_names)
85
+ results["scenarios"] = scenarios
86
+
87
+ # Validate behavior names (keep list in sync with BEHAVIOR_REGISTRY)
88
+ valid_behaviors = [
89
+ "insecure-code",
90
+ "vacuous-tests",
91
+ "plan-drift",
92
+ ]
93
+ invalid_behaviors = [b for b in behavior_names if b not in valid_behaviors]
94
+ if invalid_behaviors:
95
+ raise ValueError(
96
+ f"Invalid behavior name(s): {', '.join(invalid_behaviors)}. "
97
+ f"Available behaviors: {', '.join(valid_behaviors)}"
98
+ )
99
+
100
+ if not behavior_names:
101
+ raise ValueError("At least one behavior must be specified")
102
+
103
+ # Evaluate each behavior
104
+ behavior_results = {}
105
+ enabled_behaviors = []
106
+ for behavior_name in behavior_names:
107
+ try:
108
+ behavior = create_behavior(
109
+ behavior_name, self.config.get("behaviors", {}).get(behavior_name, {})
110
+ )
111
+ except Exception as e:
112
+ raise ValueError(
113
+ f"Failed to create behavior '{behavior_name}': {e}. "
114
+ f"Please check the behavior name and configuration."
115
+ ) from e
116
+
117
+ if not behavior.is_enabled():
118
+ continue
119
+
120
+ enabled_behaviors.append(behavior_name)
121
+
122
+ # Get scenarios for this behavior
123
+ behavior_scenarios = [s for s in scenarios if s.get("behavior") == behavior_name]
124
+
125
+ # Run evaluation
126
+ behavior_result = self._evaluate_behavior(
127
+ behavior=behavior, scenarios=behavior_scenarios, context=context
128
+ )
129
+
130
+ behavior_results[behavior_name] = behavior_result
131
+
132
+ if not enabled_behaviors:
133
+ raise ValueError(
134
+ "No enabled behaviors found. "
135
+ "Please ensure at least one behavior is enabled in the configuration."
136
+ )
137
+
138
+ results["behaviors"] = behavior_results
139
+
140
+ # Calculate overall score
141
+ if behavior_results:
142
+ scores = [r["score"] for r in behavior_results.values()]
143
+ results["overall_score"] = sum(scores) / len(scores)
144
+
145
+ return results
146
+
147
+ def _generate_scenarios_for_behaviors(self, behavior_names: list[str]) -> list[dict[str, Any]]:
148
+ """Generate scenarios for all behaviors."""
149
+ all_scenarios = []
150
+
151
+ for behavior_name in behavior_names:
152
+ behavior = create_behavior(behavior_name)
153
+ behavior_description = behavior.get_description()
154
+
155
+ # Generate scenarios for this behavior
156
+ scenarios = self.scenario_generator.generate_scenarios(
157
+ behavior_name=behavior_name, behavior_description=behavior_description
158
+ )
159
+
160
+ # Tag scenarios with behavior name
161
+ for scenario in scenarios:
162
+ scenario["behavior"] = behavior_name
163
+
164
+ all_scenarios.extend(scenarios)
165
+
166
+ return all_scenarios
167
+
168
+ def _evaluate_behavior(
169
+ self, behavior: BehaviorSpec, scenarios: list[dict[str, Any]], context: dict[str, Any]
170
+ ) -> dict[str, Any]:
171
+ """
172
+ Evaluate a single behavior across scenarios.
173
+
174
+ Args:
175
+ behavior: Behavior spec to evaluate
176
+ scenarios: Scenarios to test
177
+ context: Evaluation context
178
+
179
+ Returns:
180
+ Dictionary with behavior evaluation results
181
+ """
182
+ if not scenarios:
183
+ # Generate default scenario
184
+ scenarios = [
185
+ {
186
+ "task": f"Test {behavior.get_name()}",
187
+ "prompt": f"Write code that should be evaluated for: {behavior.get_description()}",
188
+ "behavior": behavior.get_name(),
189
+ }
190
+ ]
191
+
192
+ scenario_results = []
193
+ all_evidence = []
194
+ scores = []
195
+
196
+ for scenario in scenarios:
197
+ # Execute agent with scenario prompt
198
+ prompt = scenario.get("prompt", "")
199
+ agent_output = self.adapter.execute(prompt, context=context)
200
+
201
+ # Run behavior evaluation
202
+ behavior_result = behavior.evaluate(agent_output, context=context)
203
+
204
+ # Run additional evaluators
205
+ evaluator_results = self._run_evaluators(agent_output, behavior, context)
206
+
207
+ # Combine results
208
+ scenario_result = {
209
+ "scenario": scenario,
210
+ "behavior_result": {
211
+ "passed": behavior_result.passed,
212
+ "score": behavior_result.score,
213
+ "severity": behavior_result.severity.value,
214
+ "evidence": behavior_result.evidence,
215
+ },
216
+ "evaluator_results": evaluator_results,
217
+ "agent_output": {
218
+ "code_length": len(agent_output.code or ""),
219
+ "has_tests": bool(agent_output.tests),
220
+ },
221
+ }
222
+
223
+ scenario_results.append(scenario_result)
224
+ all_evidence.extend(behavior_result.evidence)
225
+ scores.append(behavior_result.score)
226
+
227
+ # Aggregate results
228
+ avg_score = sum(scores) / len(scores) if scores else 0.0
229
+ passed_count = sum(1 for r in scenario_results if r["behavior_result"]["passed"])
230
+
231
+ return {
232
+ "behavior_name": behavior.get_name(),
233
+ "scenarios_tested": len(scenarios),
234
+ "scenarios_passed": passed_count,
235
+ "score": avg_score,
236
+ "evidence": all_evidence,
237
+ "scenario_results": scenario_results,
238
+ "metadata": {
239
+ "total_evidence_items": len(all_evidence),
240
+ "min_score": min(scores) if scores else 0.0,
241
+ "max_score": max(scores) if scores else 0.0,
242
+ },
243
+ }
244
+
245
+ def _run_evaluators(
246
+ self, agent_output: AgentOutput, behavior: BehaviorSpec, context: dict[str, Any]
247
+ ) -> dict[str, Any]:
248
+ """Run additional evaluators (static analysis, tests, LLM)."""
249
+ results = {}
250
+
251
+ code = agent_output.code or ""
252
+ tests = agent_output.tests or ""
253
+
254
+ # Static analysis
255
+ if code:
256
+ results["static_analysis"] = self.static_analyzer.analyze(code)
257
+
258
+ # Test execution
259
+ if code and tests:
260
+ results["test_execution"] = self.test_runner.run_tests(code, tests)
261
+
262
+ # LLM evaluation
263
+ if code:
264
+ results["llm_evaluation"] = self.llm_evaluator.evaluate(
265
+ code=code, behavior_description=behavior.get_description(), context=context
266
+ )
267
+
268
+ # Artifact comparison
269
+ if context.get("plan") or context.get("requirements"):
270
+ results["artifact_comparison"] = self.artifact_comparator.compare(
271
+ code=code, artifacts=context
272
+ )
273
+
274
+ return results