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,148 @@
1
+ """JDBC, JPA/Hibernate, Spring Data, and MyBatis detector."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+ from typing import List, Optional, Set
7
+
8
+ from tree_sitter import Node, Tree
9
+
10
+ from adl_drift.lang.java.detectors.base import DeclaredTypes, JavaDetector
11
+ from adl_drift.lang.java.scopes import TypeScopes
12
+ from adl_drift.utils.types import Pattern, PatternCategory
13
+
14
+ JDBC_TYPES = {"Statement", "PreparedStatement", "CallableStatement", "Connection"}
15
+ JDBC_STATEMENT_TYPES = {"Statement", "PreparedStatement", "CallableStatement"}
16
+ JPA_METHODS = {"createQuery", "createNativeQuery", "persist", "merge", "remove", "find", "save"}
17
+ MYBATIS_METHODS = {"selectOne", "selectList", "insert", "update", "delete"}
18
+ PERSISTENCE_PACKAGES = ("jakarta.persistence", "javax.persistence")
19
+ MYBATIS_PACKAGE = "org.apache.ibatis.annotations"
20
+ MYBATIS_SESSION_TYPES = {
21
+ "SqlSession": "org.apache.ibatis.session.SqlSession",
22
+ "SqlSessionTemplate": "org.mybatis.spring.SqlSessionTemplate",
23
+ }
24
+ JDBC_TYPE_NAMES = {name: f"java.sql.{name}" for name in JDBC_TYPES}
25
+ JPA_TYPE_NAMES = {
26
+ "EntityManager": ("jakarta.persistence.EntityManager", "javax.persistence.EntityManager"),
27
+ "Session": ("org.hibernate.Session",),
28
+ "StatelessSession": ("org.hibernate.StatelessSession",),
29
+ }
30
+
31
+
32
+ class JavaDataAccessDetector(JavaDetector):
33
+ @property
34
+ def category(self) -> PatternCategory:
35
+ return PatternCategory.DATA_ACCESS
36
+
37
+ def detect(self, tree: Tree, source_bytes: bytes, file_path: Path) -> List[Pattern]:
38
+ root = tree.root_node
39
+ imports = self._imports(root, source_bytes)
40
+ types = self._declared_types(root, source_bytes)
41
+ local_types = self._declared_type_names(root, source_bytes)
42
+ patterns: List[Pattern] = []
43
+ for node in self._walk(root):
44
+ if node.type == "method_invocation":
45
+ pattern = self._call(
46
+ node, source_bytes, file_path, types, imports, local_types
47
+ )
48
+ if pattern:
49
+ patterns.append(pattern)
50
+ elif node.type in ("class_declaration", "interface_declaration"):
51
+ patterns.extend(self._declaration(node, source_bytes, file_path, imports))
52
+ elif node.type == "method_declaration":
53
+ pattern = self._mybatis_annotation(node, source_bytes, file_path, imports)
54
+ if pattern:
55
+ patterns.append(pattern)
56
+ return patterns
57
+
58
+ def _call(
59
+ self, node: Node, source: bytes, path: Path,
60
+ types: DeclaredTypes, imports: Set[str], local_types: TypeScopes,
61
+ ) -> Optional[Pattern]:
62
+ method = self._text(node.child_by_field_name("name"), source)
63
+ receiver = self._text(node.child_by_field_name("object"), source)
64
+ raw_declared = self._declared_type(receiver, types, node)
65
+ declared = self._simple_type(raw_declared)
66
+ jdbc_type = JDBC_TYPE_NAMES.get(declared)
67
+ jdbc_method = (
68
+ declared in JDBC_STATEMENT_TYPES and method.startswith("execute")
69
+ ) or (declared == "Connection" and method == "prepareStatement")
70
+ if jdbc_type and jdbc_method and self._type_has_provenance(
71
+ raw_declared, imports, local_types, jdbc_type, node
72
+ ):
73
+ return self._finding("jdbc_operation", "jdbc", method, node, path, 0.95,
74
+ "JDBC receiver type and operation match")
75
+ jpa_type_names = JPA_TYPE_NAMES.get(declared, ())
76
+ if method in JPA_METHODS and any(
77
+ self._type_has_provenance(raw_declared, imports, local_types, qualified, node)
78
+ for qualified in jpa_type_names
79
+ ):
80
+ return self._finding("jpa_operation", "jpa", method, node, path, 0.93,
81
+ "JPA/Hibernate receiver type and operation match")
82
+ mybatis_type = MYBATIS_SESSION_TYPES.get(declared)
83
+ if mybatis_type and method in MYBATIS_METHODS and self._type_has_provenance(
84
+ raw_declared, imports, local_types, mybatis_type, node
85
+ ):
86
+ return self._finding("mybatis_operation", "mybatis", method, node, path, 0.93,
87
+ "MyBatis session receiver type and operation match")
88
+ return None
89
+
90
+ def _declaration(
91
+ self, node: Node, source: bytes, path: Path, imports: Set[str]
92
+ ) -> List[Pattern]:
93
+ patterns: List[Pattern] = []
94
+ for annotation in self._annotations(node):
95
+ if any(self._annotation_is(annotation, source, imports, f"{p}.Entity")
96
+ for p in PERSISTENCE_PACKAGES):
97
+ patterns.append(self._finding(
98
+ "jpa_entity", "jpa", "entity", node, path, 0.98,
99
+ "Class is annotated with the imported JPA @Entity annotation",
100
+ ))
101
+ if self._annotation_is(annotation, source, imports, f"{MYBATIS_PACKAGE}.Mapper"):
102
+ patterns.append(self._finding(
103
+ "mybatis_mapper", "mybatis", "mapper", node, path, 0.98,
104
+ "Type is annotated with MyBatis @Mapper",
105
+ ))
106
+
107
+ repository_types = {
108
+ "JpaRepository": "org.springframework.data.jpa.repository.JpaRepository",
109
+ "CrudRepository": "org.springframework.data.repository.CrudRepository",
110
+ "PagingAndSortingRepository": "org.springframework.data.repository.PagingAndSortingRepository",
111
+ }
112
+ if node.type == "interface_declaration" and any(
113
+ f"{marker}<" in self._text(node, source)
114
+ and self._has_import(imports, qualified)
115
+ for marker, qualified in repository_types.items()
116
+ ):
117
+ patterns.append(self._finding(
118
+ "spring_data_repository", "spring-data", "repository", node, path, 0.96,
119
+ "Interface extends a Spring Data repository contract",
120
+ ))
121
+ return patterns
122
+
123
+ def _mybatis_annotation(
124
+ self, node: Node, source: bytes, path: Path, imports: Set[str]
125
+ ) -> Optional[Pattern]:
126
+ for annotation in self._annotations(node):
127
+ name = self._annotation_name(annotation, source).rsplit(".", 1)[-1]
128
+ if name in {"Select", "Insert", "Update", "Delete"} and self._annotation_is(
129
+ annotation, source, imports, f"{MYBATIS_PACKAGE}.{name}"
130
+ ):
131
+ return self._finding(
132
+ "mybatis_statement", "mybatis", name.lower(), node, path, 0.98,
133
+ f"Method uses imported MyBatis @{name} SQL annotation",
134
+ )
135
+ return None
136
+
137
+ def _finding(
138
+ self, name: str, framework: str, operation: str, node: Node,
139
+ path: Path, confidence: float, reason: str,
140
+ ) -> Pattern:
141
+ return self._make_pattern(
142
+ name=name,
143
+ description=f"{framework} data access: {operation}",
144
+ node=node,
145
+ file_path=path,
146
+ confidence=confidence,
147
+ metadata={"framework": framework, "operation": operation, "reason": reason},
148
+ )
@@ -0,0 +1,53 @@
1
+ """Java import classification detector."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import re
6
+ from pathlib import Path
7
+ from typing import List
8
+
9
+ from tree_sitter import Tree
10
+
11
+ from adl_drift.lang.java.detectors.base import JavaDetector
12
+ from adl_drift.utils.types import Pattern, PatternCategory
13
+
14
+ IMPORT_RE = re.compile(r"import\s+(static\s+)?([\w.]+)(\.\*)?\s*;")
15
+
16
+
17
+ class JavaImportDetector(JavaDetector):
18
+ @property
19
+ def category(self) -> PatternCategory:
20
+ return PatternCategory.IMPORTS
21
+
22
+ def detect(self, tree: Tree, source_bytes: bytes, file_path: Path) -> List[Pattern]:
23
+ patterns: List[Pattern] = []
24
+ for node in tree.root_node.named_children:
25
+ if node.type != "import_declaration":
26
+ continue
27
+ match = IMPORT_RE.fullmatch(self._text(node, source_bytes))
28
+ if not match:
29
+ continue
30
+ is_static = bool(match.group(1))
31
+ module = match.group(2)
32
+ is_wildcard = bool(match.group(3))
33
+ import_type = "jdk" if module.startswith(("java.", "javax.", "jakarta.")) else "third_party"
34
+ reason = (
35
+ "Import belongs to the JDK/Jakarta namespace"
36
+ if import_type == "jdk"
37
+ else "Import is outside the JDK/Jakarta namespace"
38
+ )
39
+ patterns.append(self._make_pattern(
40
+ name="wildcard_import" if is_wildcard else f"import:{import_type}",
41
+ description=f"Java {import_type} import: {module}",
42
+ node=node,
43
+ file_path=file_path,
44
+ confidence=0.99,
45
+ metadata={
46
+ "import_type": import_type,
47
+ "module": module,
48
+ "is_static": is_static,
49
+ "is_wildcard": is_wildcard,
50
+ "reason": reason,
51
+ },
52
+ ))
53
+ return patterns
@@ -0,0 +1,159 @@
1
+ """Spring MVC and JAX-RS route detector."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import re
6
+ from pathlib import Path
7
+ from typing import List, Optional, Set, Tuple
8
+
9
+ from tree_sitter import Node, Tree
10
+
11
+ from adl_drift.lang.java.detectors.base import JavaDetector
12
+ from adl_drift.lang.java.extractor import JavaSymbolExtractor
13
+ from adl_drift.utils.types import Pattern, PatternCategory
14
+
15
+ SPRING_PACKAGE = "org.springframework.web.bind.annotation"
16
+ JAX_PACKAGES = ("jakarta.ws.rs", "javax.ws.rs")
17
+ SPRING_METHODS = {
18
+ "GetMapping": "GET",
19
+ "PostMapping": "POST",
20
+ "PutMapping": "PUT",
21
+ "PatchMapping": "PATCH",
22
+ "DeleteMapping": "DELETE",
23
+ }
24
+ JAX_METHODS = {name: name for name in ("GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS")}
25
+
26
+
27
+ class JavaRouteDetector(JavaDetector):
28
+ @property
29
+ def category(self) -> PatternCategory:
30
+ return PatternCategory.ROUTES
31
+
32
+ def detect(self, tree: Tree, source_bytes: bytes, file_path: Path) -> List[Pattern]:
33
+ root = tree.root_node
34
+ imports = self._imports(root, source_bytes)
35
+ extractor = JavaSymbolExtractor(self.parser)
36
+ anon_index = extractor._index_anonymous_classes(root)
37
+ patterns: List[Pattern] = []
38
+
39
+ for method in self._walk(root):
40
+ if method.type != "method_declaration":
41
+ continue
42
+ owner = self._enclosing_class(method)
43
+ class_annotations = self._annotations(owner) if owner is not None else []
44
+ method_annotations = self._annotations(method)
45
+
46
+ routes = self._spring_routes(
47
+ class_annotations, method_annotations, source_bytes, imports
48
+ )
49
+ if not routes:
50
+ jax_route = self._jax_route(
51
+ class_annotations, method_annotations, source_bytes, imports
52
+ )
53
+ routes = [jax_route] if jax_route is not None else []
54
+ if not routes:
55
+ continue
56
+
57
+ method_symbol = extractor._extract_method(
58
+ method, source_bytes, file_path, anon_index
59
+ )
60
+ if method_symbol is None:
61
+ continue
62
+ handler_id = self._handler_id(
63
+ self._package(root, source_bytes), method_symbol.scope_chain,
64
+ method_symbol.name, method_symbol.signature_key or "()",
65
+ )
66
+ for framework, http_method, class_path, method_path in routes:
67
+ route_path = self._join_paths(class_path, method_path)
68
+ reason = f"{framework} route annotation on method {method_symbol.name}"
69
+ patterns.append(self._make_pattern(
70
+ name=f"route:{http_method}",
71
+ description=f"{framework} {http_method} route: {route_path}",
72
+ node=method,
73
+ file_path=file_path,
74
+ confidence=0.98,
75
+ metadata={
76
+ "framework": framework,
77
+ "http_method": http_method,
78
+ "route_path": route_path,
79
+ "class_path": class_path,
80
+ "method_path": method_path,
81
+ "handler": method_symbol.name,
82
+ "handler_id": handler_id,
83
+ "entry_point": True,
84
+ "reason": reason,
85
+ },
86
+ ))
87
+ return patterns
88
+
89
+ def _spring_routes(
90
+ self, class_annotations: List[Node], method_annotations: List[Node],
91
+ source: bytes, imports: Set[str],
92
+ ) -> List[Tuple[str, str, str, str]]:
93
+ class_path = ""
94
+ for annotation in class_annotations:
95
+ if self._annotation_is(annotation, source, imports, f"{SPRING_PACKAGE}.RequestMapping"):
96
+ class_path = self._path(annotation, source)
97
+
98
+ for annotation in method_annotations:
99
+ name = self._annotation_name(annotation, source).rsplit(".", 1)[-1]
100
+ if name in SPRING_METHODS and self._annotation_is(
101
+ annotation, source, imports, f"{SPRING_PACKAGE}.{name}"
102
+ ):
103
+ return [("spring", SPRING_METHODS[name], class_path, self._path(annotation, source))]
104
+ if name == "RequestMapping" and self._annotation_is(
105
+ annotation, source, imports, f"{SPRING_PACKAGE}.RequestMapping"
106
+ ):
107
+ methods = re.findall(
108
+ r"RequestMethod\.(GET|POST|PUT|PATCH|DELETE|HEAD|OPTIONS)",
109
+ self._text(annotation, source),
110
+ )
111
+ return [
112
+ ("spring", method, class_path, self._path(annotation, source))
113
+ for method in methods or ["ANY"]
114
+ ]
115
+ return []
116
+
117
+ def _jax_route(
118
+ self, class_annotations: List[Node], method_annotations: List[Node],
119
+ source: bytes, imports: Set[str],
120
+ ) -> Optional[Tuple[str, str, str, str]]:
121
+ class_path = self._jax_path(class_annotations, source, imports)
122
+ method_path = self._jax_path(method_annotations, source, imports)
123
+ for annotation in method_annotations:
124
+ name = self._annotation_name(annotation, source).rsplit(".", 1)[-1]
125
+ for package in JAX_PACKAGES:
126
+ if name in JAX_METHODS and self._annotation_is(
127
+ annotation, source, imports, f"{package}.{name}"
128
+ ):
129
+ return "jax-rs", JAX_METHODS[name], class_path, method_path
130
+ return None
131
+
132
+ def _jax_path(self, annotations: List[Node], source: bytes, imports: Set[str]) -> str:
133
+ for annotation in annotations:
134
+ if any(self._annotation_is(annotation, source, imports, f"{p}.Path") for p in JAX_PACKAGES):
135
+ return self._path(annotation, source)
136
+ return ""
137
+
138
+ def _path(self, annotation: Node, source: bytes) -> str:
139
+ strings = self._annotation_strings(annotation, source)
140
+ return strings[0] if strings else ""
141
+
142
+ @staticmethod
143
+ def _enclosing_class(node: Node) -> Optional[Node]:
144
+ parent = node.parent
145
+ while parent is not None and parent.type not in (
146
+ "class_declaration", "interface_declaration", "record_declaration"
147
+ ):
148
+ parent = parent.parent
149
+ return parent
150
+
151
+ @staticmethod
152
+ def _join_paths(class_path: str, method_path: str) -> str:
153
+ parts = [part.strip("/") for part in (class_path, method_path) if part.strip("/")]
154
+ return "/" + "/".join(parts) if parts else "/"
155
+
156
+ @staticmethod
157
+ def _handler_id(package: str, scope: List[str], name: str, signature: str) -> str:
158
+ qualified = ".".join([*scope, f"{name}{signature}"])
159
+ return f"{package}:{qualified}"