adl-toolkit 0.0.0-stage → 0.1.1

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 (104) hide show
  1. package/INSTALL_TOOLKIT.md +163 -0
  2. package/README.md +269 -2
  3. package/adl_drift/__init__.py +6 -0
  4. package/adl_drift/cli/__init__.py +1 -0
  5. package/adl_drift/cli/commands.py +993 -0
  6. package/adl_drift/cli/main.py +744 -0
  7. package/adl_drift/cortex/__init__.py +1 -0
  8. package/adl_drift/cortex/embeddings.py +96 -0
  9. package/adl_drift/cortex/memory.py +1749 -0
  10. package/adl_drift/cortex/patterns.py +89 -0
  11. package/adl_drift/cortex/telemetry.py +192 -0
  12. package/adl_drift/detectors/__init__.py +1 -0
  13. package/adl_drift/detectors/async_patterns.py +115 -0
  14. package/adl_drift/detectors/base.py +121 -0
  15. package/adl_drift/detectors/config.py +122 -0
  16. package/adl_drift/detectors/data_access.py +128 -0
  17. package/adl_drift/detectors/decorators.py +122 -0
  18. package/adl_drift/detectors/dynamic_patterns.py +260 -0
  19. package/adl_drift/detectors/errors.py +134 -0
  20. package/adl_drift/detectors/imports.py +132 -0
  21. package/adl_drift/detectors/logging_patterns.py +108 -0
  22. package/adl_drift/detectors/routes.py +257 -0
  23. package/adl_drift/detectors/security.py +236 -0
  24. package/adl_drift/detectors/taint.py +406 -0
  25. package/adl_drift/detectors/tests.py +95 -0
  26. package/adl_drift/detectors/type_conventions.py +81 -0
  27. package/adl_drift/graph/__init__.py +1 -0
  28. package/adl_drift/graph/builder.py +2670 -0
  29. package/adl_drift/graph/cfg.py +461 -0
  30. package/adl_drift/graph/cluster.py +40 -0
  31. package/adl_drift/graph/edges.py +86 -0
  32. package/adl_drift/graph/node.py +90 -0
  33. package/adl_drift/harness/__init__.py +10 -0
  34. package/adl_drift/harness/setup.py +832 -0
  35. package/adl_drift/harness/skills/drift-antigravity.md +19 -0
  36. package/adl_drift/harness/skills/drift-generic.md +19 -0
  37. package/adl_drift/harness/skills/drift.md +25 -0
  38. package/adl_drift/harness/watch.py +84 -0
  39. package/adl_drift/lang/__init__.py +8 -0
  40. package/adl_drift/lang/base.py +464 -0
  41. package/adl_drift/lang/java/__init__.py +6 -0
  42. package/adl_drift/lang/java/backend.py +97 -0
  43. package/adl_drift/lang/java/cfg.py +1424 -0
  44. package/adl_drift/lang/java/config.py +707 -0
  45. package/adl_drift/lang/java/detectors/__init__.py +29 -0
  46. package/adl_drift/lang/java/detectors/additional.py +445 -0
  47. package/adl_drift/lang/java/detectors/base.py +154 -0
  48. package/adl_drift/lang/java/detectors/data_access.py +148 -0
  49. package/adl_drift/lang/java/detectors/imports.py +53 -0
  50. package/adl_drift/lang/java/detectors/routes.py +159 -0
  51. package/adl_drift/lang/java/detectors/security.py +386 -0
  52. package/adl_drift/lang/java/detectors/tests.py +74 -0
  53. package/adl_drift/lang/java/extractor.py +582 -0
  54. package/adl_drift/lang/java/ids.py +99 -0
  55. package/adl_drift/lang/java/parser.py +49 -0
  56. package/adl_drift/lang/java/resolution/__init__.py +1 -0
  57. package/adl_drift/lang/java/resolution/constants.py +1619 -0
  58. package/adl_drift/lang/java/resolution/constructors.py +278 -0
  59. package/adl_drift/lang/java/resolution/context.py +305 -0
  60. package/adl_drift/lang/java/resolution/dispatch.py +540 -0
  61. package/adl_drift/lang/java/resolution/functional.py +1318 -0
  62. package/adl_drift/lang/java/resolution/generated.py +95 -0
  63. package/adl_drift/lang/java/resolution/model.py +153 -0
  64. package/adl_drift/lang/java/resolution/orchestration.py +866 -0
  65. package/adl_drift/lang/java/resolution/overloads.py +1275 -0
  66. package/adl_drift/lang/java/resolution/scopes.py +1111 -0
  67. package/adl_drift/lang/java/resolution/types.py +2947 -0
  68. package/adl_drift/lang/java/resolver.py +1401 -0
  69. package/adl_drift/lang/java/scopes.py +69 -0
  70. package/adl_drift/lang/java/taint.py +500 -0
  71. package/adl_drift/lang/java/type_provenance.py +342 -0
  72. package/adl_drift/lang/python/__init__.py +8 -0
  73. package/adl_drift/lang/python/backend.py +94 -0
  74. package/adl_drift/lang/python/cfg.py +537 -0
  75. package/adl_drift/lang/python/extractor.py +454 -0
  76. package/adl_drift/lang/python/parser.py +41 -0
  77. package/adl_drift/lang/python/resolver.py +1789 -0
  78. package/adl_drift/lang/python/taint.py +365 -0
  79. package/adl_drift/lang/registry.py +117 -0
  80. package/adl_drift/parser/__init__.py +1 -0
  81. package/adl_drift/parser/cache.py +38 -0
  82. package/adl_drift/parser/core.py +33 -0
  83. package/adl_drift/parser/extractor.py +17 -0
  84. package/adl_drift/parser/literal_eval.py +326 -0
  85. package/adl_drift/scanner/__init__.py +1 -0
  86. package/adl_drift/scanner/discovery.py +338 -0
  87. package/adl_drift/scanner/incremental.py +261 -0
  88. package/adl_drift/scanner/parallel.py +224 -0
  89. package/adl_drift/server/__init__.py +1 -0
  90. package/adl_drift/server/handlers.py +2849 -0
  91. package/adl_drift/server/mcp_server.py +127 -0
  92. package/adl_drift/server/mcp_stdio.py +335 -0
  93. package/adl_drift/server/protocol.py +71 -0
  94. package/adl_drift/server/static/index.html +696 -0
  95. package/adl_drift/server/web.py +350 -0
  96. package/adl_drift/utils/__init__.py +1 -0
  97. package/adl_drift/utils/config.py +77 -0
  98. package/adl_drift/utils/fswalk.py +52 -0
  99. package/adl_drift/utils/types.py +345 -0
  100. package/bin/adl-drift.mjs +35 -0
  101. package/bin/adl-toolkit.mjs +488 -0
  102. package/package.json +29 -4
  103. package/pyproject.toml +49 -0
  104. package/scripts/install_toolkit.py +3584 -0
@@ -0,0 +1,132 @@
1
+ """Import pattern detectors.
2
+
3
+ Analyzes import structure, unused imports, circular dependencies.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ from typing import List, Dict, Any, Set
9
+ from pathlib import Path
10
+ from tree_sitter import Node, Tree
11
+
12
+ from adl_drift.detectors.base import BaseDetector
13
+ from adl_drift.utils.types import Pattern, PatternCategory
14
+ from adl_drift.parser.core import PythonParser
15
+
16
+
17
+ class ImportDetector(BaseDetector):
18
+ """Detects and analyzes import patterns."""
19
+
20
+ STDLIB_MODULES = {
21
+ "os", "sys", "json", "re", "collections", "itertools", "functools",
22
+ "pathlib", "typing", "dataclasses", "enum", "abc", "inspect",
23
+ "threading", "multiprocessing", "asyncio", "unittest", "logging",
24
+ "hashlib", "random", "datetime", "time", "math", "statistics",
25
+ }
26
+
27
+ def __init__(self, parser: PythonParser):
28
+ super().__init__(parser)
29
+
30
+ @property
31
+ def category(self) -> PatternCategory:
32
+ return PatternCategory.IMPORTS
33
+
34
+ def detect(self, tree: Tree, source_bytes: bytes, file_path: Path) -> List[Pattern]:
35
+ patterns = []
36
+
37
+ for node in self._walk(tree.root_node):
38
+ if node.type == "import_statement":
39
+ patterns.extend(self._analyze_import(node, source_bytes, file_path))
40
+ elif node.type == "import_from_statement":
41
+ patterns.extend(self._analyze_from_import(node, source_bytes, file_path))
42
+
43
+ return patterns
44
+
45
+ def _analyze_import(self, node: Node, source: bytes, path: Path) -> List[Pattern]:
46
+ patterns = []
47
+
48
+ for child in node.children:
49
+ if child.type == "dotted_name":
50
+ name = self.parser.node_text(child, source)
51
+ top_module = name.split(".")[0]
52
+ category = self._classify_module(top_module)
53
+
54
+ patterns.append(self._make_pattern(
55
+ name=f"import:{category}",
56
+ description=f"Import {category}: {name}",
57
+ node=node,
58
+ file_path=path,
59
+ confidence=0.95,
60
+ metadata={
61
+ "import_type": category,
62
+ "module": name,
63
+ "top_module": top_module,
64
+ }
65
+ ))
66
+
67
+ return patterns
68
+
69
+ def _analyze_from_import(self, node: Node, source: bytes, path: Path) -> List[Pattern]:
70
+ patterns = []
71
+
72
+ module_node = node.child_by_field_name("module_name")
73
+ module = self.parser.node_text(module_node, source) if module_node else ""
74
+
75
+ node_text = self.parser.node_text(node, source)
76
+ if "import *" in node_text:
77
+ patterns.append(self._make_pattern(
78
+ name="star_import",
79
+ description=f"Star import from {module} (pollutes namespace)",
80
+ node=node,
81
+ file_path=path,
82
+ confidence=0.9,
83
+ metadata={
84
+ "import_type": "star",
85
+ "module": module,
86
+ "severity": "warning",
87
+ }
88
+ ))
89
+
90
+ if module.startswith("."):
91
+ patterns.append(self._make_pattern(
92
+ name="relative_import",
93
+ description=f"Relative import: {module}",
94
+ node=node,
95
+ file_path=path,
96
+ confidence=0.95,
97
+ metadata={
98
+ "import_type": "relative",
99
+ "module": module,
100
+ }
101
+ ))
102
+
103
+ top_module = module.lstrip(".").split(".")[0] if module else ""
104
+ category = self._classify_module(top_module)
105
+
106
+ patterns.append(self._make_pattern(
107
+ name=f"from_import:{category}",
108
+ description=f"From import {category}: {module}",
109
+ node=node,
110
+ file_path=path,
111
+ confidence=0.9,
112
+ metadata={
113
+ "import_type": category,
114
+ "module": module,
115
+ "top_module": top_module,
116
+ }
117
+ ))
118
+
119
+ return patterns
120
+
121
+ def _classify_module(self, name: str) -> str:
122
+ if name in self.STDLIB_MODULES:
123
+ return "stdlib"
124
+ elif name in {"__future__"}:
125
+ return "future"
126
+ else:
127
+ return "third_party"
128
+
129
+ def _walk(self, node):
130
+ yield node
131
+ for child in node.children:
132
+ yield from self._walk(child)
@@ -0,0 +1,108 @@
1
+ """Logging detector — print(), logger calls, eager-format anti-pattern."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+ from typing import List, Optional
7
+
8
+ from tree_sitter import Node, Tree
9
+
10
+ from adl_drift.detectors.base import BaseDetector
11
+ from adl_drift.utils.types import Pattern, PatternCategory
12
+
13
+
14
+ LOG_LEVELS = {"debug", "info", "warning", "warn", "error", "critical", "exception"}
15
+
16
+
17
+ class LoggingDetector(BaseDetector):
18
+ """Flags print, logger calls, and f-string args to logger methods."""
19
+
20
+ @property
21
+ def category(self) -> PatternCategory:
22
+ return PatternCategory.LOGGING
23
+
24
+ def detect(self, tree: Tree, source_bytes: bytes, file_path: Path) -> List[Pattern]:
25
+ patterns: List[Pattern] = []
26
+ has_logger_init = False
27
+
28
+ for node in self._walk(tree.root_node):
29
+ if node.type != "call":
30
+ continue
31
+ func = node.child_by_field_name("function")
32
+ if func is None:
33
+ continue
34
+ func_text = self.parser.node_text(func, source_bytes).strip()
35
+
36
+ if func_text == "print":
37
+ patterns.append(self._make_pattern(
38
+ name="print_call",
39
+ description="print() — prefer logger",
40
+ node=node, file_path=file_path, confidence=0.5,
41
+ metadata={"severity": "LOW"},
42
+ ))
43
+ continue
44
+
45
+ if func_text == "logging.getLogger":
46
+ has_logger_init = True
47
+ continue
48
+
49
+ level = self._level_from_attr(func, source_bytes)
50
+ if level is None:
51
+ continue
52
+ eager = self._has_fstring_arg(node, source_bytes)
53
+ name = "logger_fstring" if eager else "logger_call"
54
+ desc = (
55
+ f"logger.{level}(f\"...\") — eager-format anti-pattern"
56
+ if eager else f"logger.{level}() call"
57
+ )
58
+ patterns.append(self._make_pattern(
59
+ name=name,
60
+ description=desc,
61
+ node=node, file_path=file_path,
62
+ confidence=0.7 if eager else 0.6,
63
+ metadata={"level": level, "eager_format": eager},
64
+ ))
65
+
66
+ if has_logger_init:
67
+ patterns.append(self._make_pattern(
68
+ name="logger_init",
69
+ description="Module initializes a logger via logging.getLogger",
70
+ node=tree.root_node, file_path=file_path, confidence=0.95,
71
+ metadata={"role": "logger_module"},
72
+ ))
73
+ return patterns
74
+
75
+ def _level_from_attr(self, func_node: Node, source: bytes) -> Optional[str]:
76
+ if func_node.type != "attribute":
77
+ return None
78
+ attr = func_node.child_by_field_name("attribute")
79
+ if attr is None:
80
+ return None
81
+ name = self.parser.node_text(attr, source)
82
+ if name in LOG_LEVELS:
83
+ obj = func_node.child_by_field_name("object")
84
+ if obj is not None:
85
+ obj_text = self.parser.node_text(obj, source)
86
+ if obj_text in {"logger", "log", "logging", "LOG", "LOGGER"} or \
87
+ obj_text.endswith("_logger") or obj_text.endswith(".logger"):
88
+ return name
89
+ return None
90
+
91
+ def _has_fstring_arg(self, call_node: Node, source: bytes) -> bool:
92
+ args = call_node.child_by_field_name("arguments")
93
+ if args is None:
94
+ return False
95
+ for c in args.children:
96
+ if c.type == "string":
97
+ # tree-sitter-python uses interpolation child for f-strings
98
+ for sub in c.children:
99
+ if sub.type == "interpolation":
100
+ return True
101
+ if self.parser.node_text(c, source).lstrip().startswith(("f'", 'f"', "rf", "fr")):
102
+ return True
103
+ return False
104
+
105
+ def _walk(self, node: Node):
106
+ yield node
107
+ for child in node.children:
108
+ yield from self._walk(child)
@@ -0,0 +1,257 @@
1
+ """HTTP route detectors for Flask, FastAPI, Django.
2
+
3
+ Finds URL endpoints and their handler functions.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ from typing import List, Dict, Any, Optional
9
+ from pathlib import Path
10
+ from tree_sitter import Node, Tree
11
+
12
+ from adl_drift.detectors.base import BaseDetector
13
+ from adl_drift.utils.types import Pattern, PatternCategory, Symbol
14
+ from adl_drift.parser.core import PythonParser
15
+
16
+
17
+ class RouteDetector(BaseDetector):
18
+ """Detects HTTP route definitions across frameworks.
19
+
20
+ Handles:
21
+ - Flask: @app.route("/path")
22
+ - FastAPI: @app.get("/path"), @router.post("/path")
23
+ - Django: path("/path", views.handler) in urls.py
24
+ - Starlette: @app.route("/path")
25
+ """
26
+
27
+ # Known HTTP method names used by frameworks
28
+ HTTP_METHODS = {
29
+ "route", "get", "post", "put", "patch", "delete",
30
+ "head", "options", "trace", "connect",
31
+ "api_route", "include_router",
32
+ }
33
+
34
+ def __init__(self, parser: PythonParser):
35
+ super().__init__(parser)
36
+
37
+ @property
38
+ def category(self) -> PatternCategory:
39
+ return PatternCategory.ROUTES
40
+
41
+ def detect(self, tree: Tree, source_bytes: bytes, file_path: Path) -> List[Pattern]:
42
+ patterns = []
43
+
44
+ for node in self._walk(tree.root_node):
45
+ if node.type != "function_definition":
46
+ continue
47
+
48
+ decorators = []
49
+ seen = set()
50
+ # tree-sitter-python wraps decorators in decorated_definition; they are siblings
51
+ for dec_node in (node.prev_sibling, *node.children):
52
+ if dec_node is None:
53
+ continue
54
+ if dec_node.type == "decorator":
55
+ dec_info = self._parse_decorator(dec_node, source_bytes)
56
+ if dec_info and dec_info.get("is_route"):
57
+ key = dec_info.get("raw", "")
58
+ if key not in seen:
59
+ seen.add(key)
60
+ decorators.append(dec_info)
61
+ # Also walk up to parent decorated_definition to catch all decorators
62
+ if node.parent and node.parent.type == "decorated_definition":
63
+ for sib in node.parent.children:
64
+ if sib.type == "decorator" and sib is not dec_node:
65
+ dec_info = self._parse_decorator(sib, source_bytes)
66
+ if dec_info and dec_info.get("is_route"):
67
+ key = dec_info.get("raw", "")
68
+ if key not in seen:
69
+ seen.add(key)
70
+ decorators.append(dec_info)
71
+
72
+ if not decorators:
73
+ continue
74
+
75
+ func_name = self._get_func_name(node, source_bytes)
76
+
77
+ for dec in decorators:
78
+ pattern = self._make_pattern(
79
+ name=f"route:{dec.get('method', 'unknown').upper()}",
80
+ description=f"HTTP {dec.get('method', 'unknown').upper()} {dec.get('path', '/')} -> {func_name}",
81
+ node=node,
82
+ file_path=file_path,
83
+ confidence=0.95,
84
+ metadata={
85
+ "framework": dec.get("framework", "unknown"),
86
+ "http_method": dec.get("method", "get"),
87
+ "route_path": dec.get("path", "/"),
88
+ "handler": func_name,
89
+ "decorator": dec.get("raw", ""),
90
+ }
91
+ )
92
+ patterns.append(pattern)
93
+
94
+ return patterns
95
+
96
+ def _parse_decorator(self, node: Node, source: bytes) -> Optional[Dict[str, Any]]:
97
+ """Parse a decorator node to see if it's a route definition."""
98
+ text = self.parser.node_text(node, source).strip()
99
+
100
+ call_node = None
101
+ for child in node.children:
102
+ if child.type == "call":
103
+ call_node = child
104
+ break
105
+
106
+ if not call_node:
107
+ return None
108
+
109
+ func_node = call_node.child_by_field_name("function")
110
+ if not func_node:
111
+ return None
112
+
113
+ func_text = self.parser.node_text(func_node, source)
114
+
115
+ method = None
116
+ framework = None
117
+
118
+ if func_node.type == "attribute":
119
+ obj_node = func_node.child_by_field_name("object")
120
+ attr_node = func_node.child_by_field_name("attribute")
121
+
122
+ if obj_node and attr_node:
123
+ obj_name = self.parser.node_text(obj_node, source)
124
+ attr_name = self.parser.node_text(attr_node, source)
125
+
126
+ if attr_name.lower() in self.HTTP_METHODS:
127
+ method = attr_name.lower()
128
+ framework = self._detect_framework(obj_name, source)
129
+ elif func_node.type == "identifier":
130
+ name = self.parser.node_text(func_node, source)
131
+ if name.lower() in self.HTTP_METHODS:
132
+ method = name.lower()
133
+ framework = "unknown"
134
+
135
+ if not method:
136
+ return None
137
+
138
+ args_node = call_node.child_by_field_name("arguments")
139
+ path = "/"
140
+ if args_node:
141
+ for arg in args_node.children:
142
+ if arg.type == "string":
143
+ path = self.parser.node_text(arg, source).strip("'\"")
144
+ break
145
+
146
+ # If the decorator is `@app.route(...)` (generic), look for a
147
+ # `methods=[...]` keyword arg and use the first listed method.
148
+ if method == "route" and args_node:
149
+ for arg in args_node.children:
150
+ if arg.type != "keyword_argument":
151
+ continue
152
+ name_node = arg.child_by_field_name("name")
153
+ value_node = arg.child_by_field_name("value")
154
+ if not (name_node and value_node):
155
+ continue
156
+ if self.parser.node_text(name_node, source) != "methods":
157
+ continue
158
+ for v in value_node.children:
159
+ if v.type == "string":
160
+ verb = self.parser.node_text(v, source).strip("'\"").lower()
161
+ if verb in self.HTTP_METHODS:
162
+ method = verb
163
+ break
164
+ break
165
+
166
+ return {
167
+ "is_route": True,
168
+ "method": method,
169
+ "path": path,
170
+ "framework": framework,
171
+ "raw": text,
172
+ }
173
+
174
+ def _detect_framework(self, obj_name: str, source: bytes) -> str:
175
+ """Guess framework from app variable name."""
176
+ if obj_name in {"app", "application"}:
177
+ return "flask_or_fastapi"
178
+ elif obj_name == "router":
179
+ return "fastapi"
180
+ elif obj_name == "blueprint":
181
+ return "flask"
182
+ return "unknown"
183
+
184
+ def _get_func_name(self, node: Node, source: bytes) -> str:
185
+ """Extract function name from definition node."""
186
+ name_node = node.child_by_field_name("name")
187
+ if name_node:
188
+ return self.parser.node_text(name_node, source)
189
+ return "<anonymous>"
190
+
191
+ def _walk(self, node: Node):
192
+ yield node
193
+ for child in node.children:
194
+ yield from self._walk(child)
195
+
196
+
197
+ class DjangoURLDetector(BaseDetector):
198
+ """Detects Django URL patterns in urls.py files."""
199
+
200
+ def __init__(self, parser: PythonParser):
201
+ super().__init__(parser)
202
+
203
+ @property
204
+ def category(self) -> PatternCategory:
205
+ return PatternCategory.ROUTES
206
+
207
+ def detect(self, tree: Tree, source_bytes: bytes, file_path: Path) -> List[Pattern]:
208
+ patterns = []
209
+ if file_path.name != "urls.py":
210
+ return patterns
211
+
212
+ for node in self._walk(tree.root_node):
213
+ if node.type != "call":
214
+ continue
215
+
216
+ func_node = node.child_by_field_name("function")
217
+ if not func_node:
218
+ continue
219
+
220
+ func_name = self.parser.node_text(func_node, source_bytes)
221
+ if func_name not in {"path", "re_path", "url"}:
222
+ continue
223
+
224
+ args_node = node.child_by_field_name("arguments")
225
+ if not args_node or len(args_node.children) < 2:
226
+ continue
227
+
228
+ route = "/"
229
+ view = "unknown"
230
+
231
+ for arg in args_node.children:
232
+ if arg.type == "string":
233
+ route = self.parser.node_text(arg, source_bytes).strip("'""")
234
+ elif arg.type in ("identifier", "attribute"):
235
+ view = self.parser.node_text(arg, source_bytes)
236
+
237
+ pattern = self._make_pattern(
238
+ name=f"django_url:{func_name}",
239
+ description=f"Django URL: {route} -> {view}",
240
+ node=node,
241
+ file_path=file_path,
242
+ confidence=0.9,
243
+ metadata={
244
+ "framework": "django",
245
+ "url_type": func_name,
246
+ "route": route,
247
+ "view": view,
248
+ }
249
+ )
250
+ patterns.append(pattern)
251
+
252
+ return patterns
253
+
254
+ def _walk(self, node):
255
+ yield node
256
+ for child in node.children:
257
+ yield from self._walk(child)