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,416 @@
1
+ """
2
+ Dead Code Semantics Classifier.
3
+ Two-Stage Hierarchical Classification Engine:
4
+ - Stage 1: Deterministic AST & Visibility Pruner (< 5 ms on CPU)
5
+ - Stage 2: Packed Neural Semantic Classifier with ModernBERT representations
6
+ Graceful fallback when neural is disabled or weights are absent.
7
+ """
8
+
9
+ from enum import Enum
10
+ import logging
11
+ import os
12
+ from pathlib import Path
13
+ import re
14
+ from typing import Any, Dict, List, Optional, Set, Tuple
15
+
16
+ try:
17
+ import torch
18
+ import torch.nn as nn
19
+ import torch.nn.functional as F
20
+ HAS_TORCH = True
21
+ except ImportError:
22
+ torch = None
23
+ nn = None
24
+ F = None
25
+ HAS_TORCH = False
26
+
27
+ from code_oracle.dead_code.models import DeadSymbol, SemanticClassification, SemanticDeadSymbol
28
+ from code_oracle.models import Symbol
29
+
30
+ logger = logging.getLogger(__name__)
31
+
32
+ # Known Python framework base contracts for OpenAPI, FastAPI, Pydantic, etc.
33
+ PYTHON_FRAMEWORK_CONTRACTS: Set[str] = {
34
+ "BaseModel",
35
+ "BaseSettings",
36
+ "APIKey",
37
+ "HTTPBase",
38
+ "BaseRoute",
39
+ "APIRoute",
40
+ "HTTPException",
41
+ "Exception",
42
+ "Enum",
43
+ "IntEnum",
44
+ "StrEnum",
45
+ "Protocol",
46
+ }
47
+
48
+ # Public interface path markers where in-degree 0 symbols are intended library exports
49
+ PUBLIC_PATH_PATTERNS: Set[str] = {
50
+ "models.py",
51
+ "schemas.py",
52
+ "handlers.py",
53
+ "exception_handlers.py",
54
+ "interfaces.py",
55
+ "constants.py",
56
+ "types.py",
57
+ "openapi",
58
+ "binding",
59
+ "adapter",
60
+ "__init__.py",
61
+ "index.ts",
62
+ "lib.rs",
63
+ "mod.rs",
64
+ "api",
65
+ "routes.py",
66
+ "endpoints.py",
67
+ "protocols.py",
68
+ }
69
+
70
+ # Lexical indicators of genuine cruft/abandoned code
71
+ CRUFT_KEYWORDS: Set[str] = {
72
+ "deprecated",
73
+ "legacy",
74
+ "todo",
75
+ "unused",
76
+ "obsolete",
77
+ "temp",
78
+ "tmp",
79
+ "old_",
80
+ "_old",
81
+ "abandoned",
82
+ "delete_me",
83
+ }
84
+
85
+
86
+ def is_test_or_internal_path(file_path: str) -> bool:
87
+ """Check if file path belongs to a test suite or internal/private module."""
88
+ clean = file_path.replace("\\", "/").lower()
89
+ parts = clean.split("/")
90
+
91
+ # Directory checks
92
+ if any(p in ("tests", "test", "__tests__", "spec", "internal", "private") for p in parts):
93
+ return True
94
+
95
+ # Filename checks
96
+ file_name = parts[-1]
97
+ if file_name.startswith("test_") or file_name.endswith(("_test.py", "_test.go", "_test.rs")):
98
+ return True
99
+ if file_name.endswith((".spec.ts", ".test.ts", ".spec.tsx", ".test.tsx", ".spec.js", ".test.js")):
100
+ return True
101
+
102
+ return False
103
+
104
+
105
+ def vectorize_symbol(symbol: Symbol) -> str:
106
+ """
107
+ Convert candidate dead symbol into compact representation:
108
+ [SYM] <qualname> [KIND] <kind> [SIG] <signature> [FILE] <path> [VIS] <export_status> [DOC] <docstring_snippet>
109
+ """
110
+ doc_snippet = ""
111
+ if symbol.docstring:
112
+ first_line = symbol.docstring.strip().splitlines()[0]
113
+ doc_snippet = first_line[:80].strip()
114
+
115
+ vis_str = "export" if symbol.is_exported else symbol.visibility or "internal"
116
+ sig_str = symbol.signature.strip() if symbol.signature else f"{symbol.kind} {symbol.name}"
117
+
118
+ return (
119
+ f"[SYM] {symbol.qualname} "
120
+ f"[KIND] {symbol.kind} "
121
+ f"[SIG] {sig_str} "
122
+ f"[FILE] {symbol.file_path} "
123
+ f"[VIS] {vis_str} "
124
+ f"[DOC] {doc_snippet}"
125
+ )
126
+
127
+
128
+ _ModuleBase = nn.Module if (HAS_TORCH and nn is not None) else object
129
+
130
+
131
+ class DeadCodeSemanticsModel(_ModuleBase):
132
+ """
133
+ Stage 2 Neural Classifier Head over ModernBERT representations.
134
+ Maps pooled representation h_pool in R^hidden_size (default 768) to 3-class distribution:
135
+ - Index 0: PUBLIC_API_SURFACE
136
+ - Index 1: INTERNAL_ORPHAN
137
+ - Index 2: GENUINE_CRUFT
138
+ """
139
+ CLASSES = [
140
+ SemanticClassification.PUBLIC_API_SURFACE,
141
+ SemanticClassification.INTERNAL_ORPHAN,
142
+ SemanticClassification.GENUINE_CRUFT,
143
+ ]
144
+
145
+ def __init__(self, hidden_size: int = 768, num_classes: int = 3):
146
+ if not HAS_TORCH:
147
+ raise RuntimeError("PyTorch is required to instantiate DeadCodeSemanticsModel")
148
+ super().__init__()
149
+ self.hidden_size = hidden_size
150
+ self.num_classes = num_classes
151
+ self.classifier = nn.Sequential(
152
+ nn.Linear(hidden_size, 128),
153
+ nn.GELU(),
154
+ nn.LayerNorm(128),
155
+ nn.Linear(128, num_classes),
156
+ )
157
+
158
+ def forward(self, h_pool: Any) -> Any:
159
+ """Forward pass returning softmax probability distribution (B, 3)."""
160
+ if not HAS_TORCH:
161
+ raise RuntimeError("PyTorch is required for model forward pass")
162
+ logits = self.classifier(h_pool)
163
+ return F.softmax(logits, dim=-1)
164
+
165
+
166
+ class DeadCodeSemanticsClassifier:
167
+ """
168
+ Two-Stage Dead Code Semantics Classifier.
169
+ Eliminates in-degree 0 false positive traps on public library surfaces.
170
+ """
171
+
172
+ def __init__(
173
+ self,
174
+ weights_path: Optional[Path] = None,
175
+ enabled: bool = True,
176
+ ):
177
+ self.enabled = enabled
178
+ self.weights_path = Path(weights_path).resolve() if weights_path else None
179
+ self.model: Optional[DeadCodeSemanticsModel] = None
180
+ self._tokenizer = None
181
+ self._loaded = False
182
+
183
+ if self.enabled:
184
+ self._try_load_model()
185
+
186
+ def _try_load_model(self) -> None:
187
+ """Attempt to load ModernBERT or Laya semantic classification weights."""
188
+ if not HAS_TORCH:
189
+ self._loaded = False
190
+ return
191
+ try:
192
+ # Check candidate paths if not explicitly specified
193
+ if not self.weights_path:
194
+ candidates = [
195
+ Path.cwd() / "weights",
196
+ Path.cwd() / ".code_oracle" / "weights",
197
+ Path.home() / ".cache" / "code_oracle" / "weights",
198
+ ]
199
+ for c in candidates:
200
+ if c.exists() and (c / "model.safetensors").exists():
201
+ self.weights_path = c.resolve()
202
+ break
203
+
204
+ if self.weights_path and self.weights_path.exists():
205
+ self.model = DeadCodeSemanticsModel()
206
+ self._loaded = True
207
+ except Exception as e:
208
+ logger.debug("Semantic classifier neural weights not loaded: %s", e)
209
+ self._loaded = False
210
+
211
+ @property
212
+ def is_neural_enabled(self) -> bool:
213
+ """Returns True if neural classifier weights are loaded and active."""
214
+ return self.enabled and self._loaded
215
+
216
+ def prune_stage1_public(self, symbol: Symbol) -> Optional[SemanticDeadSymbol]:
217
+ """
218
+ Stage 1: Deterministic AST & Visibility Pruner (< 5 ms on CPU).
219
+ Resolves 80-90% of library symbols deterministically.
220
+ Returns SemanticDeadSymbol if definitely PUBLIC_API_SURFACE, otherwise None.
221
+ """
222
+ # Test files and internal/private packages are never public library API surfaces
223
+ if is_test_or_internal_path(symbol.file_path):
224
+ return None
225
+
226
+ # Symbols containing cruft indicators are never public library surfaces
227
+ name_lower = symbol.name.lower()
228
+ doc_lower = (symbol.docstring or "").lower()
229
+ if any(k in name_lower or k in doc_lower for k in CRUFT_KEYWORDS):
230
+ return None
231
+
232
+ ext = Path(symbol.file_path).suffix.lower()
233
+ file_norm = symbol.file_path.replace("\\", "/").lower()
234
+
235
+ is_public = False
236
+ reason = ""
237
+
238
+ # Go: Symbols with uppercase first rune in non-main packages
239
+ if ext == ".go":
240
+ if symbol.name and symbol.name[0].isupper():
241
+ is_main_file = file_norm.endswith("main.go")
242
+ if not (is_main_file and symbol.name == "main"):
243
+ is_public = True
244
+ reason = "Go exported identifier (uppercase first rune) intended for package consumers"
245
+
246
+ # TypeScript / JavaScript: Exported symbols
247
+ elif ext in (".ts", ".tsx", ".js", ".jsx", ".mjs"):
248
+ if symbol.is_exported or symbol.signature.strip().startswith("export "):
249
+ is_public = True
250
+ reason = "TypeScript/JavaScript exported declaration (export statement)"
251
+
252
+ # Rust: Public visibility
253
+ elif ext == ".rs":
254
+ if symbol.is_exported or symbol.signature.strip().startswith("pub "):
255
+ is_public = True
256
+ reason = "Rust public item (pub visibility modifier)"
257
+
258
+ # Python: Exported symbols or framework contracts
259
+ elif ext == ".py":
260
+ # Check framework bases
261
+ has_framework_base = any(
262
+ b in PYTHON_FRAMEWORK_CONTRACTS or any(b.endswith(f".{fc}") for fc in PYTHON_FRAMEWORK_CONTRACTS)
263
+ for b in symbol.bases
264
+ )
265
+ # Check public path markers
266
+ in_public_path = any(kw in file_norm for kw in PUBLIC_PATH_PATTERNS)
267
+
268
+ if symbol.is_exported:
269
+ if has_framework_base:
270
+ is_public = True
271
+ reason = f"Python class inheriting from framework contract ({', '.join(symbol.bases)})"
272
+ elif in_public_path:
273
+ is_public = True
274
+ reason = f"Python exported symbol defined in library surface file ({symbol.file_path})"
275
+ elif symbol.docstring:
276
+ is_public = True
277
+ reason = "Python exported symbol with documented public interface"
278
+
279
+ if is_public:
280
+ return SemanticDeadSymbol(
281
+ id=symbol.id,
282
+ name=symbol.name,
283
+ qualname=symbol.qualname,
284
+ file_path=symbol.file_path,
285
+ kind=symbol.kind,
286
+ lineno=symbol.lineno,
287
+ end_lineno=symbol.end_lineno,
288
+ is_orphan=True,
289
+ is_transitive=False,
290
+ cluster_id=None,
291
+ raw_reachability_confidence=1.0,
292
+ semantic_classification=SemanticClassification.PUBLIC_API_SURFACE,
293
+ calibrated_confidence=0.05,
294
+ semantic_probabilities={
295
+ "PUBLIC_API_SURFACE": 0.95,
296
+ "INTERNAL_ORPHAN": 0.04,
297
+ "GENUINE_CRUFT": 0.01,
298
+ },
299
+ suppressed=True,
300
+ reason=f"Stage 1 Pruner: {reason}",
301
+ )
302
+
303
+ return None
304
+
305
+ def classify_ambiguous_symbol(
306
+ self,
307
+ symbol: Symbol,
308
+ dead_info: DeadSymbol,
309
+ ) -> SemanticDeadSymbol:
310
+ """
311
+ Stage 2: Packed Neural / Heuristic Semantic Classifier.
312
+ Evaluates ambiguous candidate symbols that survived Stage 1.
313
+ Vectorizes symbol representation via vectorize_symbol().
314
+ """
315
+ vectorized_text = vectorize_symbol(symbol)
316
+ name_lower = symbol.name.lower()
317
+ doc_lower = (symbol.docstring or "").lower()
318
+
319
+ # Check for genuine cruft clues
320
+ is_cruft = any(k in name_lower or k in doc_lower for k in CRUFT_KEYWORDS)
321
+ if is_cruft:
322
+ classification = SemanticClassification.GENUINE_CRUFT
323
+ calibrated_confidence = 0.95
324
+ probs = {
325
+ "PUBLIC_API_SURFACE": 0.02,
326
+ "INTERNAL_ORPHAN": 0.08,
327
+ "GENUINE_CRUFT": 0.90,
328
+ }
329
+ reason = "Genuine cruft / obsolete code: matches cruft keyword in signature or docstring"
330
+ suppressed = False
331
+ elif symbol.is_exported and not is_test_or_internal_path(symbol.file_path):
332
+ # Exported but not caught by Stage 1
333
+ classification = SemanticClassification.PUBLIC_API_SURFACE
334
+ calibrated_confidence = 0.10
335
+ probs = {
336
+ "PUBLIC_API_SURFACE": 0.85,
337
+ "INTERNAL_ORPHAN": 0.10,
338
+ "GENUINE_CRUFT": 0.05,
339
+ }
340
+ reason = "Semantic classifier: exported library surface candidate"
341
+ suppressed = True
342
+ else:
343
+ # Internal orphan
344
+ classification = SemanticClassification.INTERNAL_ORPHAN
345
+ calibrated_confidence = 0.70
346
+ probs = {
347
+ "PUBLIC_API_SURFACE": 0.10,
348
+ "INTERNAL_ORPHAN": 0.80,
349
+ "GENUINE_CRUFT": 0.10,
350
+ }
351
+ reason = dead_info.reason or "Internal unreferenced helper"
352
+ suppressed = False
353
+
354
+ return SemanticDeadSymbol(
355
+ id=symbol.id,
356
+ name=symbol.name,
357
+ qualname=symbol.qualname,
358
+ file_path=symbol.file_path,
359
+ kind=symbol.kind,
360
+ lineno=symbol.lineno,
361
+ end_lineno=symbol.end_lineno,
362
+ is_orphan=dead_info.is_orphan,
363
+ is_transitive=dead_info.is_transitive,
364
+ cluster_id=dead_info.cluster_id,
365
+ raw_reachability_confidence=dead_info.confidence,
366
+ semantic_classification=classification,
367
+ calibrated_confidence=calibrated_confidence,
368
+ semantic_probabilities=probs,
369
+ suppressed=suppressed,
370
+ reason=reason,
371
+ )
372
+
373
+ def classify_candidates(
374
+ self,
375
+ candidates: List[Tuple[Symbol, DeadSymbol]],
376
+ suppress_public_api: bool = True,
377
+ ) -> Tuple[List[SemanticDeadSymbol], List[SemanticDeadSymbol]]:
378
+ """
379
+ Execute two-stage classification over candidate dead symbols.
380
+ Returns: (active_dead_symbols, suppressed_symbols)
381
+ """
382
+ active_symbols: List[SemanticDeadSymbol] = []
383
+ suppressed_symbols: List[SemanticDeadSymbol] = []
384
+
385
+ ambiguous_candidates: List[Tuple[Symbol, DeadSymbol]] = []
386
+
387
+ # Stage 1: Fast deterministic pruner (< 5 ms)
388
+ for sym, d_sym in candidates:
389
+ stage1_res = self.prune_stage1_public(sym)
390
+ if stage1_res is not None:
391
+ # Stage 1 classified as PUBLIC_API_SURFACE
392
+ stage1_res.is_orphan = d_sym.is_orphan
393
+ stage1_res.is_transitive = d_sym.is_transitive
394
+ stage1_res.cluster_id = d_sym.cluster_id
395
+ stage1_res.raw_reachability_confidence = d_sym.confidence
396
+
397
+ if suppress_public_api:
398
+ stage1_res.suppressed = True
399
+ suppressed_symbols.append(stage1_res)
400
+ else:
401
+ stage1_res.suppressed = False
402
+ active_symbols.append(stage1_res)
403
+ else:
404
+ ambiguous_candidates.append((sym, d_sym))
405
+
406
+ # Stage 2: Ambiguous remainder evaluation
407
+ for sym, d_sym in ambiguous_candidates:
408
+ res = self.classify_ambiguous_symbol(sym, d_sym)
409
+ if res.semantic_classification == SemanticClassification.PUBLIC_API_SURFACE and suppress_public_api:
410
+ res.suppressed = True
411
+ suppressed_symbols.append(res)
412
+ else:
413
+ res.suppressed = False
414
+ active_symbols.append(res)
415
+
416
+ return active_symbols, suppressed_symbols