code-oracle 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 (40) hide show
  1. code_oracle/__init__.py +30 -0
  2. code_oracle/cli.py +795 -0
  3. code_oracle/config.py +145 -0
  4. code_oracle/dataset.py +5325 -0
  5. code_oracle/dead_code/__init__.py +32 -0
  6. code_oracle/dead_code/detector.py +379 -0
  7. code_oracle/dead_code/entrypoints.py +333 -0
  8. code_oracle/dead_code/models.py +255 -0
  9. code_oracle/dead_code/semantics.py +416 -0
  10. code_oracle/decision.py +906 -0
  11. code_oracle/engine.py +430 -0
  12. code_oracle/export_onnx.py +436 -0
  13. code_oracle/hook.py +531 -0
  14. code_oracle/indexer.py +894 -0
  15. code_oracle/languages/__init__.py +114 -0
  16. code_oracle/languages/common.py +127 -0
  17. code_oracle/languages/go.py +395 -0
  18. code_oracle/languages/python.py +336 -0
  19. code_oracle/languages/rust.py +474 -0
  20. code_oracle/languages/typescript.py +775 -0
  21. code_oracle/linearizer.py +166 -0
  22. code_oracle/locator.py +301 -0
  23. code_oracle/models.py +237 -0
  24. code_oracle/perf_lint/__init__.py +38 -0
  25. code_oracle/perf_lint/engine.py +234 -0
  26. code_oracle/perf_lint/models.py +229 -0
  27. code_oracle/perf_lint/rules/__init__.py +31 -0
  28. code_oracle/perf_lint/rules/async_blocking.py +143 -0
  29. code_oracle/perf_lint/rules/n_plus_one.py +232 -0
  30. code_oracle/perf_lint/rules/nested_loops.py +137 -0
  31. code_oracle/perf_lint/rules/unclosed_res.py +494 -0
  32. code_oracle/perf_lint/visitor.py +299 -0
  33. code_oracle/server.py +184 -0
  34. code_oracle/slicer.py +225 -0
  35. code_oracle/symbolic.py +459 -0
  36. code_oracle-0.1.0.dist-info/METADATA +225 -0
  37. code_oracle-0.1.0.dist-info/RECORD +40 -0
  38. code_oracle-0.1.0.dist-info/WHEEL +4 -0
  39. code_oracle-0.1.0.dist-info/entry_points.txt +2 -0
  40. code_oracle-0.1.0.dist-info/licenses/LICENSE +190 -0
@@ -0,0 +1,299 @@
1
+ """
2
+ Multi-Language Tree-sitter AST Visitor for Performance Anti-Patterns & Resource Leaks.
3
+ Inspects concrete syntax trees across Python, TypeScript/JavaScript, Go, and Rust for:
4
+ - PERF001: Nested Loops Complexity (O(N^2) warning, O(N^3) error)
5
+ - PERF002: N+1 I/O & database calls inside loop bodies
6
+ - PERF003: Resource Leak / Unclosed Descriptors
7
+ - PERF004: Blocking Synchronous Calls in Async Context
8
+ """
9
+
10
+ from pathlib import Path
11
+ import re
12
+ from typing import Any, Dict, List, Optional, Set, Tuple
13
+
14
+ from tree_sitter import Language, Node, Parser
15
+ import tree_sitter_go
16
+ import tree_sitter_javascript
17
+ import tree_sitter_python
18
+ import tree_sitter_rust
19
+ import tree_sitter_typescript
20
+
21
+ from code_oracle.languages import detect_language
22
+ from code_oracle.languages.go import get_go_parser
23
+ from code_oracle.languages.rust import get_rust_parser
24
+ from code_oracle.languages.typescript import get_ts_parser
25
+ from code_oracle.perf_lint.models import PerfDiagnostic, PerfRule, Severity
26
+ from code_oracle.perf_lint.rules.async_blocking import (
27
+ AsyncBlockingRule,
28
+ check_async_blocking,
29
+ )
30
+ from code_oracle.perf_lint.rules.n_plus_one import (
31
+ DB_IO_METHODS,
32
+ DIRECT_IO_FUNCS,
33
+ HTTP_IO_PREFIXES,
34
+ NPlusOneRule,
35
+ check_n_plus_one,
36
+ extract_method_name,
37
+ )
38
+ from code_oracle.perf_lint.rules.nested_loops import (
39
+ FUNCTION_NODE_TYPES,
40
+ LOOP_NODE_TYPES,
41
+ NestedLoopsRule,
42
+ check_nested_loop,
43
+ )
44
+ from code_oracle.perf_lint.rules.unclosed_res import (
45
+ UnclosedResourceRule,
46
+ check_unclosed_resource,
47
+ )
48
+
49
+ # Language parsers cache
50
+ _PY_LANG = Language(tree_sitter_python.language())
51
+ _PY_PARSER = Parser(_PY_LANG)
52
+
53
+
54
+ def get_parser(language: str, file_path: str = "") -> Optional[Parser]:
55
+ """Retrieve Tree-sitter parser for target language."""
56
+ if language == "python":
57
+ return _PY_PARSER
58
+ elif language in ("typescript", "javascript"):
59
+ return get_ts_parser(file_path)
60
+ elif language == "go":
61
+ return get_go_parser()
62
+ elif language == "rust":
63
+ return get_rust_parser()
64
+ return None
65
+
66
+
67
+ # Backward-compatible helper aliases
68
+ _extract_method_name = extract_method_name
69
+ _is_perf002_io_call = NPlusOneRule.is_io_call
70
+ _is_perf003_open_call = UnclosedResourceRule.is_open_call
71
+ _is_scoped_by_context_manager = UnclosedResourceRule.is_scoped
72
+ _is_perf004_blocking_call = AsyncBlockingRule.is_blocking_call
73
+ _is_async_func_node = AsyncBlockingRule.is_async_func_node
74
+
75
+ # Call node types per language
76
+ CALL_NODE_TYPES: Dict[str, Set[str]] = {
77
+ "python": {"call"},
78
+ "typescript": {"call_expression"},
79
+ "javascript": {"call_expression"},
80
+ "go": {"call_expression"},
81
+ "rust": {"call_expression"},
82
+ }
83
+
84
+ # Inline suppression pattern
85
+ SUPPRESS_PATTERN = re.compile(
86
+ r"(?:#|//|/\*)\s*code-oracle:\s*ignore-perf(?:\s*[\(\[]([A-Za-z0-9_,\s-]+)[\)\]])?",
87
+ re.IGNORECASE,
88
+ )
89
+
90
+
91
+ def _extract_callee_text(node: Node, source_bytes: bytes) -> str:
92
+ """Extract callee name or expression from a call node."""
93
+ fn_node = node.child_by_field_name("function")
94
+ if fn_node is not None:
95
+ return source_bytes[fn_node.start_byte : fn_node.end_byte].decode("utf-8", errors="replace").strip()
96
+ if node.children:
97
+ return source_bytes[node.children[0].start_byte : node.children[0].end_byte].decode("utf-8", errors="replace").strip()
98
+ return ""
99
+
100
+
101
+ def _get_func_name(node: Node, source_bytes: bytes, lang: str) -> str:
102
+ """Extract name of function node."""
103
+ name_node = node.child_by_field_name("name")
104
+ if name_node is not None:
105
+ return source_bytes[name_node.start_byte : name_node.end_byte].decode("utf-8", errors="replace").strip()
106
+
107
+ # In TS/JS arrow function assigned to variable
108
+ if lang in ("typescript", "javascript") and node.parent and node.parent.type == "variable_declarator":
109
+ v_name = node.parent.child_by_field_name("name")
110
+ if v_name is not None:
111
+ return source_bytes[v_name.start_byte : v_name.end_byte].decode("utf-8", errors="replace").strip()
112
+
113
+ return "<anonymous>"
114
+
115
+
116
+ def _is_suppressed(
117
+ lines: List[str],
118
+ lineno: int,
119
+ rule_id: str,
120
+ extra_lines: Optional[List[int]] = None,
121
+ ) -> bool:
122
+ """
123
+ Check if a diagnostic is suppressed via inline comments:
124
+ `# code-oracle: ignore-perf` or `// code-oracle: ignore-perf`
125
+ Checks target line, preceding line (if comment-only), and optional extra lines (like root loop).
126
+ """
127
+ check_lines = [lineno]
128
+ if lineno > 1:
129
+ prev_line = lines[lineno - 2].strip()
130
+ if prev_line.startswith(("#", "//", "/*")):
131
+ check_lines.append(lineno - 1)
132
+
133
+ if extra_lines:
134
+ for el in extra_lines:
135
+ check_lines.append(el)
136
+ if el > 1:
137
+ prev_el = lines[el - 2].strip()
138
+ if prev_el.startswith(("#", "//", "/*")):
139
+ check_lines.append(el - 1)
140
+
141
+ for l_num in set(check_lines):
142
+ if 1 <= l_num <= len(lines):
143
+ line_text = lines[l_num - 1]
144
+ for match in SUPPRESS_PATTERN.finditer(line_text):
145
+ specified = match.group(1)
146
+ if not specified:
147
+ return True
148
+ rules = [r.strip().upper() for r in specified.split(",")]
149
+ if rule_id.upper() in rules:
150
+ return True
151
+
152
+ return False
153
+
154
+
155
+ class PerfLintVisitor:
156
+ """
157
+ Single-pass multi-language Tree-sitter AST visitor for performance anti-patterns.
158
+ Evaluates PERF001, PERF002, PERF003, and PERF004 across Python, TS, Go, and Rust.
159
+ """
160
+
161
+ def __init__(
162
+ self,
163
+ source: str,
164
+ file_path: str = "",
165
+ language: Optional[str] = None,
166
+ max_depth: Optional[int] = None,
167
+ ) -> None:
168
+ self.source = source
169
+ self.source_bytes = source.encode("utf-8")
170
+ self.file_path = file_path
171
+ self.language = language or detect_language(file_path) or "python"
172
+ self.max_depth = max_depth if max_depth is not None else 2
173
+ self.lines = source.splitlines()
174
+ self.diagnostics: List[PerfDiagnostic] = []
175
+
176
+ # Context stacks during traversal
177
+ self.loop_stack: List[Node] = []
178
+ self.func_stack: List[Tuple[str, bool, Node]] = [] # (name, is_async, node)
179
+
180
+ def run(self) -> List[PerfDiagnostic]:
181
+ """Execute AST visitor pass and return collected diagnostics."""
182
+ if not self.source.strip():
183
+ return []
184
+
185
+ parser = get_parser(self.language, self.file_path)
186
+ if parser is None:
187
+ return []
188
+
189
+ tree = parser.parse(self.source_bytes)
190
+ root = tree.root_node
191
+
192
+ self._visit(root)
193
+ return self.diagnostics
194
+
195
+ def _visit(self, node: Node) -> None:
196
+ """Recursive AST node visitor."""
197
+ lang = self.language
198
+ is_loop = NestedLoopsRule.is_loop_node(node, lang)
199
+ is_func = NestedLoopsRule.is_function_boundary(node, lang)
200
+ is_call = node.type in CALL_NODE_TYPES.get(lang, set())
201
+
202
+ # Handle function scope boundaries
203
+ outer_loops: Optional[List[Node]] = None
204
+ if is_func:
205
+ is_async = AsyncBlockingRule.is_async_func_node(node, lang)
206
+ func_name = _get_func_name(node, self.source_bytes, lang)
207
+ self.func_stack.append((func_name, is_async, node))
208
+ # Reset loop depth per function scope
209
+ outer_loops = self.loop_stack
210
+ self.loop_stack = []
211
+
212
+ # Handle loop entrance
213
+ loop_clauses: List[Node] = []
214
+ if is_loop:
215
+ loop_clauses = NestedLoopsRule.get_loop_clauses(node, lang)
216
+ for clause in loop_clauses:
217
+ self.loop_stack.append(clause)
218
+ current_depth = len(self.loop_stack)
219
+
220
+ # PERF001: Nested Loops Complexity
221
+ diag = NestedLoopsRule.check(
222
+ node=clause,
223
+ depth=current_depth,
224
+ max_depth=self.max_depth,
225
+ file_path=self.file_path,
226
+ lines=self.lines,
227
+ )
228
+ if diag:
229
+ enclosing_lines = [n.start_point.row + 1 for n in self.loop_stack]
230
+ if not _is_suppressed(self.lines, diag.lineno, PerfRule.PERF001.value, enclosing_lines):
231
+ self.diagnostics.append(diag)
232
+
233
+ # Handle function calls
234
+ if is_call:
235
+ self._check_call(node)
236
+
237
+ # Recurse children
238
+ for child in node.children:
239
+ self._visit(child)
240
+
241
+ # Cleanup loop exit
242
+ if is_loop:
243
+ for _ in loop_clauses:
244
+ self.loop_stack.pop()
245
+
246
+ # Cleanup function exit
247
+ if is_func:
248
+ self.func_stack.pop()
249
+ if outer_loops is not None:
250
+ self.loop_stack = outer_loops
251
+
252
+ def _check_call(self, call_node: Node) -> None:
253
+ """Inspect a call node for PERF002, PERF003, and PERF004."""
254
+ callee_text = _extract_callee_text(call_node, self.source_bytes)
255
+ if not callee_text:
256
+ return
257
+
258
+ # --- PERF002: N+1 I/O in Loop Bodies ---
259
+ if len(self.loop_stack) > 0:
260
+ diag = NPlusOneRule.check(
261
+ call_node=call_node,
262
+ callee_text=callee_text,
263
+ in_loop=True,
264
+ file_path=self.file_path,
265
+ lines=self.lines,
266
+ )
267
+ if diag:
268
+ enclosing_loop_lines = [n.start_point.row + 1 for n in self.loop_stack]
269
+ if not _is_suppressed(self.lines, diag.lineno, PerfRule.PERF002.value, enclosing_loop_lines):
270
+ self.diagnostics.append(diag)
271
+
272
+ # --- PERF003: Resource Leak / Unclosed Descriptors ---
273
+ diag = UnclosedResourceRule.check(
274
+ call_node=call_node,
275
+ callee_text=callee_text,
276
+ language=self.language,
277
+ source_bytes=self.source_bytes,
278
+ file_path=self.file_path,
279
+ lines=self.lines,
280
+ )
281
+ if diag:
282
+ if not _is_suppressed(self.lines, diag.lineno, PerfRule.PERF003.value):
283
+ self.diagnostics.append(diag)
284
+
285
+ # --- PERF004: Blocking Synchronous Calls in Async Context ---
286
+ if self.func_stack:
287
+ current_func_name, is_async, _ = self.func_stack[-1]
288
+ diag = AsyncBlockingRule.check(
289
+ call_node=call_node,
290
+ callee_text=callee_text,
291
+ is_async_context=is_async,
292
+ current_func_name=current_func_name,
293
+ language=self.language,
294
+ file_path=self.file_path,
295
+ lines=self.lines,
296
+ )
297
+ if diag:
298
+ if not _is_suppressed(self.lines, diag.lineno, PerfRule.PERF004.value):
299
+ self.diagnostics.append(diag)
code_oracle/server.py ADDED
@@ -0,0 +1,184 @@
1
+ """
2
+ Code Oracle: Lean FastMCP Server Interface.
3
+ Exposes a single minimal verification endpoint to prevent agent context bloat.
4
+ """
5
+
6
+ from pathlib import Path
7
+ from typing import Any, Dict, List, Optional
8
+
9
+ from code_oracle.engine import TopoSliceEngine
10
+
11
+ _global_engine: Optional[TopoSliceEngine] = None
12
+
13
+
14
+ def get_engine(workspace_dir: Optional[str] = None) -> TopoSliceEngine:
15
+ """Get or create singleton TopoSlice engine instance for workspace."""
16
+ global _global_engine
17
+ target_root = Path(workspace_dir).resolve() if workspace_dir else Path.cwd().resolve()
18
+ if _global_engine is None or _global_engine.workspace_root != target_root:
19
+ _global_engine = TopoSliceEngine(workspace_root=target_root)
20
+ return _global_engine
21
+
22
+
23
+ def verify_patch(
24
+ file_path: str,
25
+ patch_content: str,
26
+ workspace_dir: Optional[str] = None,
27
+ enable_neural: Optional[bool] = None,
28
+ taxonomy_threshold: float = 0.5,
29
+ ) -> Dict[str, Any]:
30
+ """
31
+ Lean verification endpoint for AI coding agents.
32
+ Evaluates AST topology and neuro-symbolic invariants in sub-50ms.
33
+ """
34
+ engine = get_engine(workspace_dir)
35
+ if enable_neural is True:
36
+ engine.decision_head.enable_neural_head()
37
+ engine.enable_neural = True
38
+ elif enable_neural is False:
39
+ engine.enable_neural = False
40
+ engine.decision_head.enabled = False
41
+
42
+ report = engine.verify(
43
+ file_path=file_path,
44
+ patch_content=patch_content,
45
+ taxonomy_threshold=taxonomy_threshold,
46
+ )
47
+ return report.to_dict()
48
+
49
+
50
+ def run_dead_code_detection(
51
+ workspace_dir: Optional[str] = None,
52
+ paths: Optional[List[str]] = None,
53
+ min_lines: int = 0,
54
+ include_unexported: bool = False,
55
+ semantic: bool = False,
56
+ suppress_api: bool = False,
57
+ neural_semantics: Optional[bool] = None,
58
+ suppress_public_api: Optional[bool] = None,
59
+ ) -> Dict[str, Any]:
60
+ """
61
+ Dead code detection endpoint for AI coding agents.
62
+ Evaluates symbol reachability graph in sub-50ms across Python, TS, Go, and Rust.
63
+ """
64
+ from code_oracle.dead_code import detect_dead_code as _detect
65
+
66
+ engine = get_engine(workspace_dir)
67
+ report = _detect(
68
+ workspace_root=engine.workspace_root,
69
+ indexer=engine.indexer,
70
+ paths=paths,
71
+ min_lines=min_lines,
72
+ include_unexported=include_unexported,
73
+ semantic=semantic,
74
+ suppress_api=suppress_api,
75
+ neural_semantics=neural_semantics,
76
+ suppress_public_api=suppress_public_api,
77
+ )
78
+ return report.to_dict()
79
+
80
+
81
+ detect_dead_code = run_dead_code_detection
82
+
83
+
84
+ def run_perf_lint(
85
+ file_path: str,
86
+ patch_content: Optional[str] = None,
87
+ workspace_dir: Optional[str] = None,
88
+ severity: str = "warn",
89
+ max_depth: Optional[int] = None,
90
+ ) -> Dict[str, Any]:
91
+ """
92
+ Performance anti-pattern and resource leak detection endpoint for AI coding agents.
93
+ Evaluates AST subtrees in sub-50ms across Python, TypeScript, Go, and Rust.
94
+ """
95
+ from code_oracle.perf_lint import lint_performance_patterns as _lint_patterns
96
+
97
+ engine = get_engine(workspace_dir)
98
+ report = _lint_patterns(
99
+ file_path=file_path,
100
+ patch_content=patch_content,
101
+ workspace_root=engine.workspace_root,
102
+ severity=severity,
103
+ max_depth=max_depth,
104
+ )
105
+ return report.to_dict()
106
+
107
+
108
+ lint_performance_patterns = run_perf_lint
109
+
110
+
111
+ def run_server():
112
+ """Start the FastMCP server."""
113
+ try:
114
+ from mcp.server.fastmcp import FastMCP
115
+
116
+ mcp = FastMCP("Code-Oracle")
117
+
118
+ @mcp.tool()
119
+ def verify_code_patch(
120
+ file_path: str,
121
+ patch_content: str,
122
+ neural: bool = False,
123
+ taxonomy_threshold: float = 0.5,
124
+ ) -> Dict[str, Any]:
125
+ """
126
+ Verify code modification topology and contract invariants in sub-50ms.
127
+ Set neural=True to activate deep Laya ModernBERT risk scoring.
128
+ """
129
+ return verify_patch(
130
+ file_path,
131
+ patch_content,
132
+ enable_neural=neural,
133
+ taxonomy_threshold=taxonomy_threshold,
134
+ )
135
+
136
+ @mcp.tool()
137
+ def detect_dead_code(
138
+ workspace_dir: Optional[str] = None,
139
+ paths: Optional[List[str]] = None,
140
+ min_lines: int = 0,
141
+ include_unexported: bool = False,
142
+ neural_semantics: bool = True,
143
+ suppress_public_api: bool = True,
144
+ ) -> Dict[str, Any]:
145
+ """
146
+ Detect unreachable, orphan, and transitively dead symbols in sub-50ms.
147
+ Multi-language support across Python, TypeScript, Go, and Rust.
148
+ """
149
+ return run_dead_code_detection(
150
+ workspace_dir=workspace_dir,
151
+ paths=paths,
152
+ min_lines=min_lines,
153
+ include_unexported=include_unexported,
154
+ neural_semantics=neural_semantics,
155
+ suppress_public_api=suppress_public_api,
156
+ )
157
+
158
+ @mcp.tool()
159
+ def lint_performance_patterns(
160
+ file_path: str,
161
+ patch_content: Optional[str] = None,
162
+ workspace_dir: Optional[str] = None,
163
+ severity: str = "warn",
164
+ max_depth: Optional[int] = None,
165
+ ) -> Dict[str, Any]:
166
+ """
167
+ Detect performance anti-patterns (nested loops, N+1 queries, resource leaks, blocking async calls) in sub-50ms.
168
+ Multi-language support across Python, TypeScript, Go, and Rust.
169
+ """
170
+ return run_perf_lint(
171
+ file_path=file_path,
172
+ patch_content=patch_content,
173
+ workspace_dir=workspace_dir,
174
+ severity=severity,
175
+ max_depth=max_depth,
176
+ )
177
+
178
+ mcp.run()
179
+ except ImportError:
180
+ print("MCP library not found. Running in standalone CLI mode.")
181
+
182
+
183
+ if __name__ == "__main__":
184
+ run_server()