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,152 @@
1
+ """pip-audit package 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 PipAuditLinter(BaseLinter):
11
+ """pip-audit package vulnerability scanner."""
12
+
13
+ def __init__(self, config: dict | None = None):
14
+ """Initialize pip-audit linter."""
15
+ super().__init__(config)
16
+ self.name = "pip-audit"
17
+
18
+ def is_available(self) -> bool:
19
+ """Check if pip-audit is available."""
20
+ try:
21
+ subprocess.run(
22
+ ["pip-audit", "--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 pip-audit on packages."""
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=["pip-audit not found in PATH. Install with: pip install pip-audit"],
43
+ execution_time=0.0,
44
+ )
45
+
46
+ try:
47
+ # Build command
48
+ cmd = ["pip-audit", "--format=json"]
49
+
50
+ # Check for requirements file
51
+ path_obj = Path(path)
52
+ if path_obj.is_file() and path_obj.name in ["requirements.txt", "requirements-dev.txt"]:
53
+ cmd.extend(["--requirement", str(path_obj)])
54
+ elif path_obj.is_dir():
55
+ req_file = path_obj / "requirements.txt"
56
+ if req_file.exists():
57
+ cmd.extend(["--requirement", str(req_file)])
58
+
59
+ result = subprocess.run(
60
+ cmd,
61
+ capture_output=True,
62
+ text=True,
63
+ timeout=60,
64
+ check=False,
65
+ )
66
+
67
+ execution_time = time.time() - start_time
68
+ return self.parse_output(
69
+ result.stdout,
70
+ result.stderr,
71
+ result.returncode,
72
+ execution_time,
73
+ )
74
+ except subprocess.TimeoutExpired:
75
+ return LinterResult(
76
+ linter=self.name,
77
+ success=False,
78
+ issues=[],
79
+ errors=["pip-audit execution timed out"],
80
+ execution_time=60.0,
81
+ )
82
+ except Exception as e:
83
+ return LinterResult(
84
+ linter=self.name,
85
+ success=False,
86
+ issues=[],
87
+ errors=[f"pip-audit error: {e!s}"],
88
+ execution_time=time.time() - start_time,
89
+ )
90
+
91
+ def parse_output(
92
+ self,
93
+ output: str,
94
+ stderr: str,
95
+ returncode: int,
96
+ execution_time: float,
97
+ ) -> LinterResult:
98
+ """Parse pip-audit JSON output."""
99
+ issues = []
100
+ errors = []
101
+
102
+ if stderr:
103
+ errors.append(stderr)
104
+
105
+ try:
106
+ data = json.loads(output) if output.strip() else {}
107
+
108
+ # pip-audit JSON format: {"vulnerabilities": [...]}
109
+ vulnerabilities = data.get("vulnerabilities", [])
110
+
111
+ for vuln in vulnerabilities:
112
+ name = vuln.get("name", "unknown")
113
+ installed_version = vuln.get("installed_version", "")
114
+ vuln_id = vuln.get("id", "")
115
+ fix_versions = vuln.get("fix_versions", [])
116
+
117
+ # Determine severity
118
+ severity = Severity.HIGH
119
+ if "critical" in str(vuln).lower():
120
+ severity = Severity.CRITICAL
121
+
122
+ message = f"{name} {installed_version}: {vuln_id}"
123
+ if fix_versions:
124
+ message += f" [Fix: {', '.join(fix_versions)}]"
125
+
126
+ issue = LinterIssue(
127
+ linter=self.name,
128
+ severity=severity,
129
+ message=message,
130
+ file="requirements.txt",
131
+ rule_id=vuln_id,
132
+ )
133
+ issues.append(issue)
134
+
135
+ return LinterResult(
136
+ linter=self.name,
137
+ success=returncode == 0 or len(issues) == 0,
138
+ issues=issues,
139
+ errors=errors,
140
+ execution_time=execution_time,
141
+ raw_output=output,
142
+ )
143
+ except json.JSONDecodeError:
144
+ errors.append("Failed to parse pip-audit JSON output")
145
+ return LinterResult(
146
+ linter=self.name,
147
+ success=False,
148
+ issues=issues,
149
+ errors=errors,
150
+ execution_time=execution_time,
151
+ raw_output=output,
152
+ )
@@ -0,0 +1,198 @@
1
+ """Pylint code quality linter integration."""
2
+
3
+ import json
4
+ import re
5
+ import subprocess
6
+ from pathlib import Path
7
+
8
+ from codeoptix.linters.base import BaseLinter, LinterIssue, LinterResult, Severity
9
+
10
+
11
+ class PylintLinter(BaseLinter):
12
+ """Pylint code quality linter."""
13
+
14
+ def __init__(self, config: dict | None = None):
15
+ """Initialize Pylint linter."""
16
+ super().__init__(config)
17
+ self.name = "pylint"
18
+ self.severity_map = {
19
+ "E": Severity.HIGH, # Error
20
+ "W": Severity.MEDIUM, # Warning
21
+ "C": Severity.LOW, # Convention
22
+ "R": Severity.LOW, # Refactor
23
+ "F": Severity.CRITICAL, # Fatal
24
+ }
25
+
26
+ def is_available(self) -> bool:
27
+ """Check if pylint is available."""
28
+ try:
29
+ subprocess.run(
30
+ ["pylint", "--version"],
31
+ capture_output=True,
32
+ timeout=5,
33
+ check=False,
34
+ )
35
+ return True
36
+ except (FileNotFoundError, subprocess.TimeoutExpired):
37
+ return False
38
+
39
+ def run(self, path: str, files: list[str] | None = None) -> LinterResult:
40
+ """Run pylint on code."""
41
+ import time
42
+
43
+ start_time = time.time()
44
+
45
+ if not self.is_available():
46
+ return LinterResult(
47
+ linter=self.name,
48
+ success=False,
49
+ issues=[],
50
+ errors=["Pylint not found in PATH. Install with: pip install pylint"],
51
+ execution_time=0.0,
52
+ )
53
+
54
+ path_obj = Path(path)
55
+ if not path_obj.exists():
56
+ return LinterResult(
57
+ linter=self.name,
58
+ success=False,
59
+ issues=[],
60
+ errors=[f"Path not found: {path}"],
61
+ execution_time=0.0,
62
+ )
63
+
64
+ # Build command
65
+ cmd = [
66
+ "pylint",
67
+ "--output-format=json",
68
+ "--disable=all",
69
+ "--enable=E,F,W,C,R", # Enable all message types
70
+ ]
71
+
72
+ # Add specific files if provided
73
+ if files:
74
+ cmd.extend(files)
75
+ # Find Python files
76
+ elif (path_obj.is_file() and path_obj.suffix == ".py") or path_obj.is_dir():
77
+ cmd.append(str(path_obj))
78
+
79
+ try:
80
+ result = subprocess.run(
81
+ cmd,
82
+ capture_output=True,
83
+ text=True,
84
+ timeout=120,
85
+ check=False,
86
+ )
87
+
88
+ execution_time = time.time() - start_time
89
+ return self.parse_output(
90
+ result.stdout,
91
+ result.stderr,
92
+ result.returncode,
93
+ execution_time,
94
+ )
95
+ except subprocess.TimeoutExpired:
96
+ return LinterResult(
97
+ linter=self.name,
98
+ success=False,
99
+ issues=[],
100
+ errors=["Pylint execution timed out"],
101
+ execution_time=120.0,
102
+ )
103
+ except Exception as e:
104
+ return LinterResult(
105
+ linter=self.name,
106
+ success=False,
107
+ issues=[],
108
+ errors=[f"Pylint error: {e!s}"],
109
+ execution_time=time.time() - start_time,
110
+ )
111
+
112
+ def parse_output(
113
+ self,
114
+ output: str,
115
+ stderr: str,
116
+ returncode: int,
117
+ execution_time: float,
118
+ ) -> LinterResult:
119
+ """Parse pylint JSON output."""
120
+ issues = []
121
+ errors = []
122
+
123
+ if stderr:
124
+ errors.append(stderr)
125
+
126
+ try:
127
+ data = json.loads(output)
128
+
129
+ for item in data:
130
+ message_type = item.get("type", "W")
131
+ severity = self.severity_map.get(message_type, Severity.MEDIUM)
132
+
133
+ issue = LinterIssue(
134
+ linter=self.name,
135
+ severity=severity,
136
+ message=item.get("message", "Unknown issue"),
137
+ file=item.get("path", ""),
138
+ line=item.get("line"),
139
+ column=item.get("column"),
140
+ code=item.get("symbol"),
141
+ rule_id=item.get("message-id"),
142
+ )
143
+ issues.append(issue)
144
+
145
+ return LinterResult(
146
+ linter=self.name,
147
+ success=returncode == 0 or len(issues) == 0,
148
+ issues=issues,
149
+ errors=errors,
150
+ execution_time=execution_time,
151
+ raw_output=output,
152
+ )
153
+ except json.JSONDecodeError:
154
+ # Try parsing text output as fallback
155
+ return self._parse_text_output(output, stderr, returncode, execution_time)
156
+
157
+ def _parse_text_output(
158
+ self,
159
+ output: str,
160
+ stderr: str,
161
+ returncode: int,
162
+ execution_time: float,
163
+ ) -> LinterResult:
164
+ """Parse pylint text output as fallback."""
165
+ issues = []
166
+ errors = []
167
+
168
+ if stderr:
169
+ errors.append(stderr)
170
+
171
+ # Pylint text format: filename:line:column: message-type: message (message-id)
172
+ pattern = r"(.+?):(\d+):(\d+):\s*([EWCRF]):\s*(.+?)\s*\((.+?)\)"
173
+
174
+ for line in output.split("\n"):
175
+ match = re.match(pattern, line)
176
+ if match:
177
+ file_path, line_num, col_num, msg_type, message, rule_id = match.groups()
178
+ severity = self.severity_map.get(msg_type, Severity.MEDIUM)
179
+
180
+ issue = LinterIssue(
181
+ linter=self.name,
182
+ severity=severity,
183
+ message=message.strip(),
184
+ file=file_path,
185
+ line=int(line_num) if line_num.isdigit() else None,
186
+ column=int(col_num) if col_num.isdigit() else None,
187
+ rule_id=rule_id,
188
+ )
189
+ issues.append(issue)
190
+
191
+ return LinterResult(
192
+ linter=self.name,
193
+ success=returncode == 0 or len(issues) == 0,
194
+ issues=issues,
195
+ errors=errors,
196
+ execution_time=execution_time,
197
+ raw_output=output,
198
+ )
@@ -0,0 +1,206 @@
1
+ """Ruff linter integration - extremely fast Python linter."""
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 RuffLinter(BaseLinter):
11
+ """Ruff linter - extremely fast Python linter written in Rust."""
12
+
13
+ def __init__(self, config: dict | None = None):
14
+ """Initialize Ruff linter."""
15
+ super().__init__(config)
16
+ self.name = "ruff"
17
+ self.severity_map = {
18
+ "E": Severity.HIGH, # Error
19
+ "W": Severity.MEDIUM, # Warning
20
+ "F": Severity.HIGH, # Pyflakes
21
+ "I": Severity.LOW, # isort
22
+ "N": Severity.LOW, # pep8-naming
23
+ "UP": Severity.LOW, # pyupgrade
24
+ "B": Severity.MEDIUM, # flake8-bugbear
25
+ "C4": Severity.LOW, # flake8-comprehensions
26
+ "SIM": Severity.LOW, # flake8-simplify
27
+ }
28
+
29
+ def is_available(self) -> bool:
30
+ """Check if ruff is available."""
31
+ try:
32
+ subprocess.run(
33
+ ["ruff", "--version"],
34
+ capture_output=True,
35
+ timeout=5,
36
+ check=False,
37
+ )
38
+ return True
39
+ except (FileNotFoundError, subprocess.TimeoutExpired):
40
+ return False
41
+
42
+ def _find_config_file(self, path: str) -> Path | None:
43
+ """Find ruff configuration file (ruff.toml, pyproject.toml, .ruff.toml)."""
44
+ path_obj = Path(path)
45
+
46
+ # Check common config locations
47
+ config_files = [
48
+ "ruff.toml",
49
+ ".ruff.toml",
50
+ "pyproject.toml",
51
+ ]
52
+
53
+ # Start from path and walk up
54
+ current = path_obj if path_obj.is_file() else path_obj.parent
55
+ while current != current.parent:
56
+ for config_file in config_files:
57
+ config_path = current / config_file
58
+ if config_path.exists():
59
+ return config_path
60
+ current = current.parent
61
+
62
+ return None
63
+
64
+ def run(self, path: str, files: list[str] | None = None) -> LinterResult:
65
+ """Run ruff on code."""
66
+ import time
67
+
68
+ start_time = time.time()
69
+
70
+ if not self.is_available():
71
+ return LinterResult(
72
+ linter=self.name,
73
+ success=False,
74
+ issues=[],
75
+ errors=[
76
+ "Ruff not found in PATH. Install with: pip install ruff or uv tool install ruff"
77
+ ],
78
+ execution_time=0.0,
79
+ )
80
+
81
+ path_obj = Path(path)
82
+ if not path_obj.exists():
83
+ return LinterResult(
84
+ linter=self.name,
85
+ success=False,
86
+ issues=[],
87
+ errors=[f"Path not found: {path}"],
88
+ execution_time=0.0,
89
+ )
90
+
91
+ # Find config file (use existing project config)
92
+ config_file = self._find_config_file(path)
93
+
94
+ # Build command
95
+ cmd = ["ruff", "check", "--output-format=json"]
96
+
97
+ # Use existing config if found
98
+ if config_file:
99
+ # Ruff automatically uses pyproject.toml or ruff.toml in project root
100
+ pass
101
+
102
+ # Add specific files if provided
103
+ if files:
104
+ cmd.extend(files)
105
+ elif (path_obj.is_file() and path_obj.suffix == ".py") or path_obj.is_dir():
106
+ cmd.append(str(path_obj))
107
+
108
+ try:
109
+ result = subprocess.run(
110
+ cmd,
111
+ capture_output=True,
112
+ text=True,
113
+ timeout=60,
114
+ check=False,
115
+ )
116
+
117
+ execution_time = time.time() - start_time
118
+ return self.parse_output(
119
+ result.stdout,
120
+ result.stderr,
121
+ result.returncode,
122
+ execution_time,
123
+ )
124
+ except subprocess.TimeoutExpired:
125
+ return LinterResult(
126
+ linter=self.name,
127
+ success=False,
128
+ issues=[],
129
+ errors=["Ruff execution timed out"],
130
+ execution_time=60.0,
131
+ )
132
+ except Exception as e:
133
+ return LinterResult(
134
+ linter=self.name,
135
+ success=False,
136
+ issues=[],
137
+ errors=[f"Ruff error: {e!s}"],
138
+ execution_time=time.time() - start_time,
139
+ )
140
+
141
+ def parse_output(
142
+ self,
143
+ output: str,
144
+ stderr: str,
145
+ returncode: int,
146
+ execution_time: float,
147
+ ) -> LinterResult:
148
+ """Parse ruff JSON output."""
149
+ issues = []
150
+ errors = []
151
+
152
+ if stderr:
153
+ errors.append(stderr)
154
+
155
+ try:
156
+ # Ruff JSON output is a list of diagnostic objects
157
+ data = json.loads(output) if output.strip() else []
158
+
159
+ for item in data:
160
+ code = item.get("code", "")
161
+ severity_str = item.get("severity", "warning")
162
+
163
+ # Map ruff severity to our severity
164
+ if severity_str == "error":
165
+ severity = Severity.HIGH
166
+ elif severity_str == "warning":
167
+ severity = Severity.MEDIUM
168
+ else:
169
+ severity = Severity.LOW
170
+
171
+ # Determine severity from code prefix if available
172
+ if code:
173
+ code_prefix = code.split(".")[0] if "." in code else code
174
+ if code_prefix in self.severity_map:
175
+ severity = self.severity_map[code_prefix]
176
+
177
+ issue = LinterIssue(
178
+ linter=self.name,
179
+ severity=severity,
180
+ message=item.get("message", "Unknown issue"),
181
+ file=item.get("filename", ""),
182
+ line=item.get("location", {}).get("row"),
183
+ column=item.get("location", {}).get("column"),
184
+ code=code,
185
+ rule_id=code,
186
+ )
187
+ issues.append(issue)
188
+
189
+ return LinterResult(
190
+ linter=self.name,
191
+ success=returncode == 0 or len(issues) == 0,
192
+ issues=issues,
193
+ errors=errors,
194
+ execution_time=execution_time,
195
+ raw_output=output,
196
+ )
197
+ except json.JSONDecodeError:
198
+ errors.append("Failed to parse ruff JSON output")
199
+ return LinterResult(
200
+ linter=self.name,
201
+ success=False,
202
+ issues=issues,
203
+ errors=errors,
204
+ execution_time=execution_time,
205
+ raw_output=output,
206
+ )