codeoptix 0.1.3__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 (92) 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 +221 -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 +1468 -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 +334 -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.3.dist-info/METADATA +295 -0
  88. codeoptix-0.1.3.dist-info/RECORD +92 -0
  89. codeoptix-0.1.3.dist-info/WHEEL +5 -0
  90. codeoptix-0.1.3.dist-info/entry_points.txt +2 -0
  91. codeoptix-0.1.3.dist-info/licenses/LICENSE +203 -0
  92. codeoptix-0.1.3.dist-info/top_level.txt +1 -0
@@ -0,0 +1,186 @@
1
+ """Linter runner that orchestrates multiple linters."""
2
+
3
+ import time
4
+ from typing import Any
5
+
6
+ from codeoptix.linters.bandit_linter import BanditLinter
7
+ from codeoptix.linters.base import BaseLinter
8
+ from codeoptix.linters.coverage_linter import CoverageLinter
9
+ from codeoptix.linters.flake8_linter import Flake8Linter
10
+ from codeoptix.linters.html_accessibility_linter import HTMLAccessibilityLinter
11
+ from codeoptix.linters.language_detector import LanguageDetector
12
+ from codeoptix.linters.mypy_linter import MypyLinter
13
+ from codeoptix.linters.pip_audit_linter import PipAuditLinter
14
+ from codeoptix.linters.pylint_linter import PylintLinter
15
+ from codeoptix.linters.ruff_linter import RuffLinter
16
+ from codeoptix.linters.safety_linter import SafetyLinter
17
+
18
+
19
+ class LinterRunner:
20
+ """Runs multiple linters and aggregates results."""
21
+
22
+ # Registry of available linters (zero new dependencies)
23
+ LINTER_REGISTRY = {
24
+ # Code Quality (fastest first)
25
+ "ruff": RuffLinter, # Fastest - should be first
26
+ "pylint": PylintLinter,
27
+ "flake8": Flake8Linter,
28
+ "mypy": MypyLinter, # Type checking
29
+ # Security
30
+ "bandit": BanditLinter,
31
+ "safety": SafetyLinter, # Dependency vulnerabilities
32
+ "pip-audit": PipAuditLinter, # Package audit
33
+ # Testing
34
+ "coverage": CoverageLinter, # Test coverage
35
+ # Accessibility (custom, no dependency)
36
+ "html-accessibility": HTMLAccessibilityLinter, # Custom HTML analyzer
37
+ }
38
+
39
+ def __init__(self, config: dict[str, Any] | None = None):
40
+ """Initialize linter runner."""
41
+ self.config = config or {}
42
+ self.linters: dict[str, BaseLinter] = {}
43
+
44
+ # Initialize enabled linters
45
+ # Default: Use all available linters (zero new dependencies)
46
+ enabled_linters = self.config.get("linters", list(self.LINTER_REGISTRY.keys()))
47
+ for linter_name in enabled_linters:
48
+ if linter_name in self.LINTER_REGISTRY:
49
+ linter_config = self.config.get("linter_config", {}).get(linter_name, {})
50
+ try:
51
+ self.linters[linter_name] = self.LINTER_REGISTRY[linter_name](linter_config)
52
+ except Exception:
53
+ # Skip linters that fail to initialize
54
+ pass
55
+
56
+ def run_linters(
57
+ self,
58
+ path: str,
59
+ linter_names: list[str] | None = None,
60
+ files: list[str] | None = None,
61
+ auto_detect: bool = True,
62
+ ) -> dict[str, Any]:
63
+ """
64
+ Run specified linters on code.
65
+
66
+ Args:
67
+ path: Path to code (file or directory)
68
+ linter_names: List of linter names to run (None = auto-detect or all enabled)
69
+ files: Optional list of specific files to check
70
+ auto_detect: Auto-detect language and select appropriate linters
71
+
72
+ Returns:
73
+ Dictionary with aggregated results
74
+ """
75
+ start_time = time.time()
76
+
77
+ # Auto-detect language and linters if requested
78
+ if linter_names is None and auto_detect:
79
+ if files:
80
+ # Detect from file list
81
+ detected_linters = LanguageDetector.get_linters_for_files(files)
82
+ # Find existing configs
83
+ config_files = LanguageDetector.find_config_files(path)
84
+ # Prioritize linters with existing configs
85
+ linter_names = []
86
+ for linter in detected_linters:
87
+ if linter in config_files or linter in self.LINTER_REGISTRY:
88
+ linter_names.append(linter)
89
+ # If no detected linters, use all available
90
+ if not linter_names:
91
+ linter_names = list(self.linters.keys())
92
+ else:
93
+ # Use all enabled linters
94
+ linter_names = list(self.linters.keys())
95
+ elif linter_names is None:
96
+ linter_names = list(self.linters.keys())
97
+
98
+ # Filter to available linters
99
+ available_linters = {
100
+ name: linter
101
+ for name, linter in self.linters.items()
102
+ if name in linter_names and linter.is_available()
103
+ }
104
+
105
+ if not available_linters:
106
+ return {
107
+ "success": False,
108
+ "errors": [
109
+ "No linters available. Install linters: pip install bandit pylint flake8"
110
+ ],
111
+ "results": {},
112
+ "summary": {
113
+ "total_issues": 0,
114
+ "critical": 0,
115
+ "high": 0,
116
+ "medium": 0,
117
+ "low": 0,
118
+ },
119
+ "execution_time": time.time() - start_time,
120
+ }
121
+
122
+ # Run each linter
123
+ results = {}
124
+ all_issues = []
125
+ all_errors = []
126
+
127
+ for linter_name, linter in available_linters.items():
128
+ try:
129
+ result = linter.run(path, files=files)
130
+ results[linter_name] = result.to_dict()
131
+ all_issues.extend(result.issues)
132
+ all_errors.extend(result.errors)
133
+ except Exception as e:
134
+ all_errors.append(f"{linter_name} error: {e!s}")
135
+ results[linter_name] = {
136
+ "success": False,
137
+ "issues": [],
138
+ "errors": [str(e)],
139
+ }
140
+
141
+ # Aggregate summary
142
+ summary = self._aggregate_summary(all_issues)
143
+
144
+ execution_time = time.time() - start_time
145
+
146
+ return {
147
+ "success": len(all_issues) == 0 and len(all_errors) == 0,
148
+ "results": results,
149
+ "summary": summary,
150
+ "issues": [issue.to_dict() for issue in all_issues],
151
+ "errors": all_errors,
152
+ "execution_time": execution_time,
153
+ }
154
+
155
+ def _aggregate_summary(self, issues: list) -> dict[str, int]:
156
+ """Aggregate issue summary by severity."""
157
+ summary = {
158
+ "total_issues": len(issues),
159
+ "critical": 0,
160
+ "high": 0,
161
+ "medium": 0,
162
+ "low": 0,
163
+ "info": 0,
164
+ }
165
+
166
+ for issue in issues:
167
+ severity = (
168
+ issue.severity if hasattr(issue, "severity") else issue.get("severity", "low")
169
+ )
170
+ if isinstance(severity, str):
171
+ severity_key = severity.lower()
172
+ else:
173
+ severity_key = severity.value.lower()
174
+
175
+ if severity_key in summary:
176
+ summary[severity_key] += 1
177
+
178
+ return summary
179
+
180
+ def get_available_linters(self) -> list[str]:
181
+ """Get list of available linters."""
182
+ return [name for name, linter in self.linters.items() if linter.is_available()]
183
+
184
+ def get_all_linters(self) -> list[str]:
185
+ """Get list of all registered linters."""
186
+ return list(self.LINTER_REGISTRY.keys())
@@ -0,0 +1,184 @@
1
+ """Safety dependency vulnerability linter integration."""
2
+
3
+ import json
4
+ import subprocess
5
+ from pathlib import Path
6
+
7
+ from codeoptix.linters.base import BaseLinter, LinterIssue, LinterResult, Severity
8
+
9
+
10
+ class SafetyLinter(BaseLinter):
11
+ """Safety dependency vulnerability scanner."""
12
+
13
+ def __init__(self, config: dict | None = None):
14
+ """Initialize Safety linter."""
15
+ super().__init__(config)
16
+ self.name = "safety"
17
+
18
+ def is_available(self) -> bool:
19
+ """Check if safety is available."""
20
+ try:
21
+ subprocess.run(
22
+ ["safety", "--version"],
23
+ capture_output=True,
24
+ timeout=5,
25
+ check=False,
26
+ )
27
+ return True
28
+ except (FileNotFoundError, subprocess.TimeoutExpired):
29
+ return False
30
+
31
+ def run(self, path: str, files: list[str] | None = None) -> LinterResult:
32
+ """Run safety on dependencies."""
33
+ import time
34
+
35
+ start_time = time.time()
36
+
37
+ if not self.is_available():
38
+ return LinterResult(
39
+ linter=self.name,
40
+ success=False,
41
+ issues=[],
42
+ errors=["Safety not found in PATH. Install with: pip install safety"],
43
+ execution_time=0.0,
44
+ )
45
+
46
+ # Safety checks requirements files or installed packages
47
+ # Find requirements files
48
+ path_obj = Path(path)
49
+ requirements_files = []
50
+
51
+ if path_obj.is_file() and path_obj.name in [
52
+ "requirements.txt",
53
+ "requirements-dev.txt",
54
+ "pyproject.toml",
55
+ ]:
56
+ requirements_files = [str(path_obj)]
57
+ elif path_obj.is_dir():
58
+ # Look for requirements files
59
+ for req_file in ["requirements.txt", "requirements-dev.txt", "pyproject.toml"]:
60
+ req_path = path_obj / req_file
61
+ if req_path.exists():
62
+ requirements_files.append(str(req_path))
63
+
64
+ if not requirements_files:
65
+ # Safety can check installed packages without requirements file
66
+ requirements_files = []
67
+
68
+ try:
69
+ # Build command
70
+ cmd = ["safety", "check", "--json"]
71
+
72
+ # Add requirements file if found
73
+ if requirements_files:
74
+ cmd.extend(["--file", requirements_files[0]])
75
+
76
+ result = subprocess.run(
77
+ cmd,
78
+ capture_output=True,
79
+ text=True,
80
+ timeout=60,
81
+ check=False,
82
+ )
83
+
84
+ execution_time = time.time() - start_time
85
+ return self.parse_output(
86
+ result.stdout,
87
+ result.stderr,
88
+ result.returncode,
89
+ execution_time,
90
+ )
91
+ except subprocess.TimeoutExpired:
92
+ return LinterResult(
93
+ linter=self.name,
94
+ success=False,
95
+ issues=[],
96
+ errors=["Safety execution timed out"],
97
+ execution_time=60.0,
98
+ )
99
+ except Exception as e:
100
+ return LinterResult(
101
+ linter=self.name,
102
+ success=False,
103
+ issues=[],
104
+ errors=[f"Safety error: {e!s}"],
105
+ execution_time=time.time() - start_time,
106
+ )
107
+
108
+ def parse_output(
109
+ self,
110
+ output: str,
111
+ stderr: str,
112
+ returncode: int,
113
+ execution_time: float,
114
+ ) -> LinterResult:
115
+ """Parse safety JSON output."""
116
+ issues = []
117
+ errors = []
118
+
119
+ if stderr:
120
+ errors.append(stderr)
121
+
122
+ try:
123
+ data = json.loads(output) if output.strip() else []
124
+
125
+ for item in data:
126
+ package = item.get("package", "unknown")
127
+ vulnerability = item.get("vulnerability", "")
128
+ installed_version = item.get("installed_version", "")
129
+ affected_versions = item.get("affected_versions", "")
130
+ cve = item.get("CVE", "")
131
+
132
+ # Safety vulnerabilities are always high/critical
133
+ severity = Severity.HIGH
134
+ if "critical" in vulnerability.lower() or "critical" in str(item).lower():
135
+ severity = Severity.CRITICAL
136
+
137
+ message = f"{package} {installed_version}: {vulnerability}"
138
+ if cve:
139
+ message += f" (CVE: {cve})"
140
+ if affected_versions:
141
+ message += f" [Affected: {affected_versions}]"
142
+
143
+ issue = LinterIssue(
144
+ linter=self.name,
145
+ severity=severity,
146
+ message=message,
147
+ file="requirements.txt", # Safety checks dependencies
148
+ rule_id=cve or f"SAFETY-{package}",
149
+ )
150
+ issues.append(issue)
151
+
152
+ return LinterResult(
153
+ linter=self.name,
154
+ success=returncode == 0 or len(issues) == 0,
155
+ issues=issues,
156
+ errors=errors,
157
+ execution_time=execution_time,
158
+ raw_output=output,
159
+ )
160
+ except json.JSONDecodeError:
161
+ # Safety might output text format
162
+ if output.strip():
163
+ for line in output.split("\n"):
164
+ if line.strip() and "is vulnerable" in line.lower():
165
+ severity = Severity.HIGH
166
+ if "critical" in line.lower():
167
+ severity = Severity.CRITICAL
168
+
169
+ issue = LinterIssue(
170
+ linter=self.name,
171
+ severity=severity,
172
+ message=line.strip(),
173
+ file="requirements.txt",
174
+ )
175
+ issues.append(issue)
176
+
177
+ return LinterResult(
178
+ linter=self.name,
179
+ success=returncode == 0 or len(issues) == 0,
180
+ issues=issues,
181
+ errors=errors,
182
+ execution_time=execution_time,
183
+ raw_output=output,
184
+ )
@@ -0,0 +1,6 @@
1
+ """Reflection engine for CodeOptiX."""
2
+
3
+ from codeoptix.reflection.engine import ReflectionEngine
4
+ from codeoptix.reflection.generator import ReflectionGenerator
5
+
6
+ __all__ = ["ReflectionEngine", "ReflectionGenerator"]
@@ -0,0 +1,70 @@
1
+ """Reflection engine for CodeOptiX."""
2
+
3
+ from typing import Any
4
+
5
+ from codeoptix.artifacts.manager import ArtifactManager
6
+ from codeoptix.reflection.generator import ReflectionGenerator
7
+
8
+
9
+ class ReflectionEngine:
10
+ """
11
+ Reflection engine that analyzes evaluation results and generates insights.
12
+
13
+ Generates human-readable reflection reports identifying root causes
14
+ and providing actionable recommendations.
15
+ """
16
+
17
+ def __init__(
18
+ self, artifact_manager: ArtifactManager | None = None, config: dict[str, Any] | None = None
19
+ ):
20
+ """
21
+ Initialize reflection engine.
22
+
23
+ Args:
24
+ artifact_manager: Artifact manager for loading/saving
25
+ config: Configuration dictionary
26
+ """
27
+ self.artifact_manager = artifact_manager or ArtifactManager()
28
+ self.config = config or {}
29
+ self.generator = ReflectionGenerator(self.config.get("generator", {}))
30
+
31
+ def reflect(
32
+ self, results: dict[str, Any], agent_name: str | None = None, save: bool = True
33
+ ) -> str:
34
+ """
35
+ Generate reflection from evaluation results.
36
+
37
+ Args:
38
+ results: Evaluation results dictionary
39
+ agent_name: Optional agent name
40
+ save: Whether to save reflection to file
41
+
42
+ Returns:
43
+ Reflection markdown content
44
+ """
45
+ # Generate reflection content
46
+ reflection_content = self.generator.generate(results, agent_name=agent_name)
47
+
48
+ # Save if requested
49
+ if save:
50
+ run_id = results.get("run_id")
51
+ self.artifact_manager.save_reflection(reflection_content, run_id=run_id)
52
+
53
+ return reflection_content
54
+
55
+ def reflect_from_run_id(self, run_id: str, agent_name: str | None = None) -> str:
56
+ """
57
+ Generate reflection from a previous evaluation run.
58
+
59
+ Args:
60
+ run_id: Run ID to load results from
61
+ agent_name: Optional agent name
62
+
63
+ Returns:
64
+ Reflection markdown content
65
+ """
66
+ # Load results
67
+ results = self.artifact_manager.load_results(run_id)
68
+
69
+ # Generate reflection
70
+ return self.reflect(results, agent_name=agent_name, save=True)
@@ -0,0 +1,209 @@
1
+ """Reflection content generation for CodeOptiX."""
2
+
3
+ from typing import Any
4
+
5
+
6
+ class ReflectionGenerator:
7
+ """Generates human-readable reflection reports from evaluation results."""
8
+
9
+ def __init__(self, config: dict[str, Any] | None = None):
10
+ """Initialize reflection generator."""
11
+ self.config = config or {}
12
+
13
+ def generate(self, results: dict[str, Any], agent_name: str | None = None) -> str:
14
+ """
15
+ Generate reflection markdown from evaluation results.
16
+
17
+ Args:
18
+ results: Evaluation results dictionary
19
+ agent_name: Optional agent name
20
+
21
+ Returns:
22
+ Reflection markdown content
23
+ """
24
+ run_id = results.get("run_id", "unknown")
25
+ timestamp = results.get("timestamp", "unknown")
26
+ overall_score = results.get("overall_score", 0.0)
27
+ behaviors = results.get("behaviors", {})
28
+
29
+ lines = []
30
+ lines.append("# CodeOptiX Reflection Report\n")
31
+ lines.append(f"**Run ID**: {run_id} \n")
32
+ lines.append(f"**Timestamp**: {timestamp} \n")
33
+ if agent_name:
34
+ lines.append(f"**Agent**: {agent_name} \n")
35
+ lines.append(f"**Overall Score**: {overall_score:.2f}/1.0 \n")
36
+ lines.append("\n---\n")
37
+
38
+ # Summary section
39
+ lines.append("## Summary\n\n")
40
+ total_behaviors = len(behaviors)
41
+ passed_behaviors = sum(1 for b in behaviors.values() if b.get("scenarios_passed", 0) > 0)
42
+ failed_behaviors = total_behaviors - passed_behaviors
43
+
44
+ lines.append(f"- **Total Behaviors Evaluated**: {total_behaviors}\n")
45
+ lines.append(f"- **Behaviors Passed**: {passed_behaviors}\n")
46
+ lines.append(f"- **Behaviors Failed**: {failed_behaviors}\n")
47
+ lines.append(f"- **Overall Score**: {overall_score:.2f}/1.0\n")
48
+ lines.append("\n")
49
+
50
+ # Behavior analysis section
51
+ lines.append("## Behavior Analysis\n\n")
52
+
53
+ for behavior_name, behavior_data in behaviors.items():
54
+ lines.append(f"### {behavior_name}\n\n")
55
+
56
+ score = behavior_data.get("score", 0.0)
57
+ scenarios_tested = behavior_data.get("scenarios_tested", 0)
58
+ scenarios_passed = behavior_data.get("scenarios_passed", 0)
59
+ scenario_results = behavior_data.get("scenario_results", [])
60
+ severity = (
61
+ scenario_results[0].get("behavior_result", {}).get("severity", "medium")
62
+ if scenario_results
63
+ else "medium"
64
+ )
65
+ evidence = behavior_data.get("evidence", [])
66
+
67
+ # Status badge
68
+ status = "✅ PASSED" if scenarios_passed > 0 else "❌ FAILED"
69
+ lines.append(f"**Status**: {status} \n")
70
+ lines.append(f"**Score**: {score:.2f}/1.0 \n")
71
+ lines.append(f"**Severity**: {severity.upper()} \n")
72
+ lines.append(f"**Scenarios Tested**: {scenarios_tested} \n")
73
+ lines.append(f"**Scenarios Passed**: {scenarios_passed} \n")
74
+ lines.append("\n")
75
+
76
+ # Evidence
77
+ if evidence:
78
+ lines.append("**Evidence**:\n")
79
+ for ev in evidence[:10]: # Limit to 10 items
80
+ lines.append(f"- {ev}\n")
81
+ if len(evidence) > 10:
82
+ lines.append(f"- ... and {len(evidence) - 10} more issues\n")
83
+ lines.append("\n")
84
+
85
+ # Root cause analysis
86
+ root_causes = self._identify_root_causes(behavior_data)
87
+ if root_causes:
88
+ lines.append("**Root Causes**:\n")
89
+ for cause in root_causes:
90
+ lines.append(f"- {cause}\n")
91
+ lines.append("\n")
92
+
93
+ # Recommendations
94
+ recommendations = self._generate_recommendations(behavior_name, behavior_data)
95
+ if recommendations:
96
+ lines.append("**Recommendations**:\n")
97
+ for rec in recommendations:
98
+ lines.append(f"- {rec}\n")
99
+ lines.append("\n")
100
+
101
+ lines.append("---\n\n")
102
+
103
+ # Overall recommendations
104
+ lines.append("## Overall Recommendations\n\n")
105
+ overall_recs = self._generate_overall_recommendations(results)
106
+ for rec in overall_recs:
107
+ lines.append(f"- {rec}\n")
108
+ lines.append("\n")
109
+
110
+ # Metadata
111
+ lines.append("## Metadata\n\n")
112
+ metadata = results.get("metadata", {})
113
+ for key, value in metadata.items():
114
+ lines.append(f"- **{key}**: {value}\n")
115
+
116
+ return "".join(lines)
117
+
118
+ def _identify_root_causes(self, behavior_data: dict[str, Any]) -> list[str]:
119
+ """Identify root causes from behavior evaluation."""
120
+ causes = []
121
+
122
+ evidence = behavior_data.get("evidence", [])
123
+ scenario_results = behavior_data.get("scenario_results", [])
124
+
125
+ # Analyze evidence patterns
126
+ if any("hardcoded" in ev.lower() for ev in evidence):
127
+ causes.append("Missing security guidelines in agent prompt")
128
+
129
+ if any("no assertions" in ev.lower() for ev in evidence):
130
+ causes.append("Insufficient test quality requirements in prompt")
131
+
132
+ if any("missing" in ev.lower() for ev in evidence):
133
+ causes.append("Agent not following planning artifacts")
134
+
135
+ # Check evaluator results
136
+ for scenario_result in scenario_results:
137
+ eval_results = scenario_result.get("evaluator_results", {})
138
+
139
+ if eval_results.get("static_analysis", {}).get("bandit"):
140
+ bandit_data = eval_results["static_analysis"]["bandit"]
141
+ if (
142
+ bandit_data
143
+ and bandit_data.get("metrics", {}).get("_totals", {}).get("SEVERITY.HIGH", 0)
144
+ > 0
145
+ ):
146
+ causes.append("Static analysis detected security vulnerabilities")
147
+
148
+ if eval_results.get("test_execution", {}).get("failed_count", 0) > 0:
149
+ causes.append("Tests are failing or have low coverage")
150
+
151
+ return list(set(causes)) # Remove duplicates
152
+
153
+ def _generate_recommendations(
154
+ self, behavior_name: str, behavior_data: dict[str, Any]
155
+ ) -> list[str]:
156
+ """Generate recommendations for a specific behavior."""
157
+ recommendations = []
158
+ score = behavior_data.get("score", 0.0)
159
+ evidence = behavior_data.get("evidence", [])
160
+
161
+ if behavior_name == "insecure-code":
162
+ if score < 0.7:
163
+ recommendations.append("Add security checklist to agent prompt")
164
+ recommendations.append("Include examples of secure code patterns")
165
+ recommendations.append("Add explicit guidance against hardcoded secrets")
166
+
167
+ elif behavior_name == "vacuous-tests":
168
+ if score < 0.7:
169
+ recommendations.append("Emphasize test quality requirements in prompt")
170
+ recommendations.append("Include examples of meaningful test assertions")
171
+ recommendations.append("Add guidance on test coverage expectations")
172
+
173
+ elif behavior_name == "plan-drift":
174
+ if score < 0.7:
175
+ recommendations.append("Strengthen alignment with planning artifacts")
176
+ recommendations.append("Add explicit requirement tracking in prompt")
177
+ recommendations.append("Include examples of plan-following behavior")
178
+
179
+ # General recommendations based on evidence
180
+ if len(evidence) > 5:
181
+ recommendations.append(
182
+ "Multiple issues detected - consider comprehensive prompt revision"
183
+ )
184
+
185
+ return recommendations
186
+
187
+ def _generate_overall_recommendations(self, results: dict[str, Any]) -> list[str]:
188
+ """Generate overall recommendations."""
189
+ recommendations = []
190
+ overall_score = results.get("overall_score", 0.0)
191
+ behaviors = results.get("behaviors", {})
192
+
193
+ if overall_score < 0.5:
194
+ recommendations.append("**Critical**: Agent performance is below acceptable threshold")
195
+ recommendations.append("Consider major prompt revision or agent configuration changes")
196
+ elif overall_score < 0.7:
197
+ recommendations.append("**Warning**: Agent performance needs improvement")
198
+ recommendations.append("Review and update agent prompts based on evidence")
199
+ else:
200
+ recommendations.append("Agent performance is acceptable but can be improved")
201
+
202
+ # Behavior-specific recommendations
203
+ failed_behaviors = [
204
+ name for name, data in behaviors.items() if data.get("score", 0.0) < 0.7
205
+ ]
206
+ if failed_behaviors:
207
+ recommendations.append(f"Focus on improving: {', '.join(failed_behaviors)}")
208
+
209
+ return recommendations
@@ -0,0 +1 @@
1
+ """Utility modules for CodeOptix."""