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.
- package/INSTALL_TOOLKIT.md +163 -0
- package/README.md +269 -2
- package/adl_drift/__init__.py +6 -0
- package/adl_drift/cli/__init__.py +1 -0
- package/adl_drift/cli/commands.py +993 -0
- package/adl_drift/cli/main.py +744 -0
- package/adl_drift/cortex/__init__.py +1 -0
- package/adl_drift/cortex/embeddings.py +96 -0
- package/adl_drift/cortex/memory.py +1749 -0
- package/adl_drift/cortex/patterns.py +89 -0
- package/adl_drift/cortex/telemetry.py +192 -0
- package/adl_drift/detectors/__init__.py +1 -0
- package/adl_drift/detectors/async_patterns.py +115 -0
- package/adl_drift/detectors/base.py +121 -0
- package/adl_drift/detectors/config.py +122 -0
- package/adl_drift/detectors/data_access.py +128 -0
- package/adl_drift/detectors/decorators.py +122 -0
- package/adl_drift/detectors/dynamic_patterns.py +260 -0
- package/adl_drift/detectors/errors.py +134 -0
- package/adl_drift/detectors/imports.py +132 -0
- package/adl_drift/detectors/logging_patterns.py +108 -0
- package/adl_drift/detectors/routes.py +257 -0
- package/adl_drift/detectors/security.py +236 -0
- package/adl_drift/detectors/taint.py +406 -0
- package/adl_drift/detectors/tests.py +95 -0
- package/adl_drift/detectors/type_conventions.py +81 -0
- package/adl_drift/graph/__init__.py +1 -0
- package/adl_drift/graph/builder.py +2670 -0
- package/adl_drift/graph/cfg.py +461 -0
- package/adl_drift/graph/cluster.py +40 -0
- package/adl_drift/graph/edges.py +86 -0
- package/adl_drift/graph/node.py +90 -0
- package/adl_drift/harness/__init__.py +10 -0
- package/adl_drift/harness/setup.py +832 -0
- package/adl_drift/harness/skills/drift-antigravity.md +19 -0
- package/adl_drift/harness/skills/drift-generic.md +19 -0
- package/adl_drift/harness/skills/drift.md +25 -0
- package/adl_drift/harness/watch.py +84 -0
- package/adl_drift/lang/__init__.py +8 -0
- package/adl_drift/lang/base.py +464 -0
- package/adl_drift/lang/java/__init__.py +6 -0
- package/adl_drift/lang/java/backend.py +97 -0
- package/adl_drift/lang/java/cfg.py +1424 -0
- package/adl_drift/lang/java/config.py +707 -0
- package/adl_drift/lang/java/detectors/__init__.py +29 -0
- package/adl_drift/lang/java/detectors/additional.py +445 -0
- package/adl_drift/lang/java/detectors/base.py +154 -0
- package/adl_drift/lang/java/detectors/data_access.py +148 -0
- package/adl_drift/lang/java/detectors/imports.py +53 -0
- package/adl_drift/lang/java/detectors/routes.py +159 -0
- package/adl_drift/lang/java/detectors/security.py +386 -0
- package/adl_drift/lang/java/detectors/tests.py +74 -0
- package/adl_drift/lang/java/extractor.py +582 -0
- package/adl_drift/lang/java/ids.py +99 -0
- package/adl_drift/lang/java/parser.py +49 -0
- package/adl_drift/lang/java/resolution/__init__.py +1 -0
- package/adl_drift/lang/java/resolution/constants.py +1619 -0
- package/adl_drift/lang/java/resolution/constructors.py +278 -0
- package/adl_drift/lang/java/resolution/context.py +305 -0
- package/adl_drift/lang/java/resolution/dispatch.py +540 -0
- package/adl_drift/lang/java/resolution/functional.py +1318 -0
- package/adl_drift/lang/java/resolution/generated.py +95 -0
- package/adl_drift/lang/java/resolution/model.py +153 -0
- package/adl_drift/lang/java/resolution/orchestration.py +866 -0
- package/adl_drift/lang/java/resolution/overloads.py +1275 -0
- package/adl_drift/lang/java/resolution/scopes.py +1111 -0
- package/adl_drift/lang/java/resolution/types.py +2947 -0
- package/adl_drift/lang/java/resolver.py +1401 -0
- package/adl_drift/lang/java/scopes.py +69 -0
- package/adl_drift/lang/java/taint.py +500 -0
- package/adl_drift/lang/java/type_provenance.py +342 -0
- package/adl_drift/lang/python/__init__.py +8 -0
- package/adl_drift/lang/python/backend.py +94 -0
- package/adl_drift/lang/python/cfg.py +537 -0
- package/adl_drift/lang/python/extractor.py +454 -0
- package/adl_drift/lang/python/parser.py +41 -0
- package/adl_drift/lang/python/resolver.py +1789 -0
- package/adl_drift/lang/python/taint.py +365 -0
- package/adl_drift/lang/registry.py +117 -0
- package/adl_drift/parser/__init__.py +1 -0
- package/adl_drift/parser/cache.py +38 -0
- package/adl_drift/parser/core.py +33 -0
- package/adl_drift/parser/extractor.py +17 -0
- package/adl_drift/parser/literal_eval.py +326 -0
- package/adl_drift/scanner/__init__.py +1 -0
- package/adl_drift/scanner/discovery.py +338 -0
- package/adl_drift/scanner/incremental.py +261 -0
- package/adl_drift/scanner/parallel.py +224 -0
- package/adl_drift/server/__init__.py +1 -0
- package/adl_drift/server/handlers.py +2849 -0
- package/adl_drift/server/mcp_server.py +127 -0
- package/adl_drift/server/mcp_stdio.py +335 -0
- package/adl_drift/server/protocol.py +71 -0
- package/adl_drift/server/static/index.html +696 -0
- package/adl_drift/server/web.py +350 -0
- package/adl_drift/utils/__init__.py +1 -0
- package/adl_drift/utils/config.py +77 -0
- package/adl_drift/utils/fswalk.py +52 -0
- package/adl_drift/utils/types.py +345 -0
- package/bin/adl-drift.mjs +35 -0
- package/bin/adl-toolkit.mjs +488 -0
- package/package.json +29 -4
- package/pyproject.toml +49 -0
- package/scripts/install_toolkit.py +3584 -0
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
"""Core dataclasses shared across the ADL Drift pipeline."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import hashlib
|
|
6
|
+
from dataclasses import dataclass, field
|
|
7
|
+
from enum import Enum, auto
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Any, Dict, List, Optional
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
# Exceptions a single bad file may raise during parse/extract/detect (P2.5).
|
|
13
|
+
# Scanners catch these per file and report them; anything else is a bug in
|
|
14
|
+
# Drift itself and should propagate.
|
|
15
|
+
SCAN_FILE_ERRORS = (UnicodeDecodeError, OSError, ValueError, RuntimeError)
|
|
16
|
+
|
|
17
|
+
# P1.10 step 2: scope-chain segment inserted after every *function* segment,
|
|
18
|
+
# mirroring CPython's __qualname__ semantics. Separates a class's method
|
|
19
|
+
# (`m.Foo.bar`) from a closure inside a same-named function
|
|
20
|
+
# (`m.Foo.<locals>.bar`).
|
|
21
|
+
LOCALS_MARKER = "<locals>"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class PatternCategory(Enum):
|
|
25
|
+
ROUTES = auto()
|
|
26
|
+
ERRORS = auto()
|
|
27
|
+
DATA_ACCESS = auto()
|
|
28
|
+
DECORATORS = auto()
|
|
29
|
+
ASYNC = auto()
|
|
30
|
+
IMPORTS = auto()
|
|
31
|
+
SECURITY = auto()
|
|
32
|
+
TESTS = auto()
|
|
33
|
+
CONFIG = auto()
|
|
34
|
+
LOGGING = auto()
|
|
35
|
+
TYPES = auto()
|
|
36
|
+
DYNAMIC = auto() # P2.12: dynamic dispatch / reflection patterns
|
|
37
|
+
TAINT = auto() # P2.19: source→sink taint paths (confirmed injection)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class ConfidenceLevel(float, Enum):
|
|
41
|
+
LOW = 0.3
|
|
42
|
+
MEDIUM = 0.6
|
|
43
|
+
HIGH = 0.9
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@dataclass
|
|
47
|
+
class SourcePosition:
|
|
48
|
+
line: int
|
|
49
|
+
column: int
|
|
50
|
+
end_line: int = 0
|
|
51
|
+
end_column: int = 0
|
|
52
|
+
|
|
53
|
+
def to_lsp_range(self) -> Dict[str, Dict[str, int]]:
|
|
54
|
+
return {
|
|
55
|
+
"start": {"line": max(self.line - 1, 0), "character": self.column},
|
|
56
|
+
"end": {"line": max(self.end_line - 1, 0), "character": self.end_column},
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
60
|
+
return {
|
|
61
|
+
"line": self.line,
|
|
62
|
+
"column": self.column,
|
|
63
|
+
"end_line": self.end_line,
|
|
64
|
+
"end_column": self.end_column,
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
@classmethod
|
|
68
|
+
def from_dict(cls, d: Dict[str, Any]) -> "SourcePosition":
|
|
69
|
+
return cls(
|
|
70
|
+
line=d["line"],
|
|
71
|
+
column=d["column"],
|
|
72
|
+
end_line=d.get("end_line", 0),
|
|
73
|
+
end_column=d.get("end_column", 0),
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
@dataclass
|
|
78
|
+
class Symbol:
|
|
79
|
+
name: str
|
|
80
|
+
kind: str # "function", "class", "method", "variable"
|
|
81
|
+
file_path: Path
|
|
82
|
+
position: SourcePosition
|
|
83
|
+
decorators: List[str] = field(default_factory=list)
|
|
84
|
+
docstring: Optional[str] = None
|
|
85
|
+
signature: Optional[str] = None
|
|
86
|
+
is_async: bool = False
|
|
87
|
+
parent: Optional[str] = None # qualified name of enclosing class
|
|
88
|
+
superclasses: List[str] = field(default_factory=list) # raw base class names for classes
|
|
89
|
+
param_types: Dict[str, str] = field(default_factory=dict) # param name -> annotation text
|
|
90
|
+
return_type: Optional[str] = None # function return annotation text
|
|
91
|
+
is_test: bool = False # pytest/unittest test function or fixture
|
|
92
|
+
metadata: Dict[str, Any] = field(default_factory=dict) # arbitrary per-symbol tags (e.g. synthetic=exec)
|
|
93
|
+
# P2.17/P1.10: full lexical scope chain — all enclosing class/function names,
|
|
94
|
+
# outermost first, with LOCALS_MARKER ("<locals>") inserted after every
|
|
95
|
+
# *function* segment. Mirrors CPython's __qualname__ minus the module prefix.
|
|
96
|
+
# e.g. for `def outer(): def inner():`, inner.scope_chain = ["outer", "<locals>"]
|
|
97
|
+
# e.g. for `class Foo: def bar(): def inner():`,
|
|
98
|
+
# inner.scope_chain = ["Foo", "bar", "<locals>"]
|
|
99
|
+
scope_chain: List[str] = field(default_factory=list)
|
|
100
|
+
implements: List[str] = field(default_factory=list) # raw interface names for classes
|
|
101
|
+
throws: List[str] = field(default_factory=list) # declared exception types for callables
|
|
102
|
+
modifiers: List[str] = field(default_factory=list) # language declaration modifiers
|
|
103
|
+
signature_key: Optional[str] = None # overload discriminator, e.g. "(String,int)"
|
|
104
|
+
|
|
105
|
+
@property
|
|
106
|
+
def qualified_name(self) -> str:
|
|
107
|
+
return f"{self.parent}.{self.name}" if self.parent else self.name
|
|
108
|
+
|
|
109
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
110
|
+
return {
|
|
111
|
+
"name": self.name,
|
|
112
|
+
"kind": self.kind,
|
|
113
|
+
"file_path": str(self.file_path),
|
|
114
|
+
"position": self.position.to_dict(),
|
|
115
|
+
"decorators": self.decorators,
|
|
116
|
+
"docstring": self.docstring,
|
|
117
|
+
"signature": self.signature,
|
|
118
|
+
"is_async": self.is_async,
|
|
119
|
+
"parent": self.parent,
|
|
120
|
+
"superclasses": self.superclasses,
|
|
121
|
+
"implements": self.implements,
|
|
122
|
+
"throws": self.throws,
|
|
123
|
+
"modifiers": self.modifiers,
|
|
124
|
+
"signature_key": self.signature_key,
|
|
125
|
+
"param_types": self.param_types,
|
|
126
|
+
"return_type": self.return_type,
|
|
127
|
+
"is_test": self.is_test,
|
|
128
|
+
"metadata": self.metadata,
|
|
129
|
+
"scope_chain": self.scope_chain,
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
@classmethod
|
|
133
|
+
def from_dict(cls, d: Dict[str, Any]) -> "Symbol":
|
|
134
|
+
return cls(
|
|
135
|
+
name=d["name"],
|
|
136
|
+
kind=d["kind"],
|
|
137
|
+
file_path=Path(d["file_path"]),
|
|
138
|
+
position=SourcePosition.from_dict(d["position"]),
|
|
139
|
+
decorators=d.get("decorators", []),
|
|
140
|
+
docstring=d.get("docstring"),
|
|
141
|
+
signature=d.get("signature"),
|
|
142
|
+
is_async=d.get("is_async", False),
|
|
143
|
+
parent=d.get("parent"),
|
|
144
|
+
superclasses=d.get("superclasses", []),
|
|
145
|
+
implements=d.get("implements", []),
|
|
146
|
+
throws=d.get("throws", []),
|
|
147
|
+
modifiers=d.get("modifiers", []),
|
|
148
|
+
signature_key=d.get("signature_key"),
|
|
149
|
+
param_types=d.get("param_types", {}),
|
|
150
|
+
return_type=d.get("return_type"),
|
|
151
|
+
is_test=d.get("is_test", False),
|
|
152
|
+
metadata=d.get("metadata", {}),
|
|
153
|
+
scope_chain=d.get("scope_chain", []),
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
@dataclass
|
|
158
|
+
class ImportRecord:
|
|
159
|
+
"""Structured import binding (P2.1). One record per imported name.
|
|
160
|
+
|
|
161
|
+
Examples:
|
|
162
|
+
import a.b → module="a.b", name=None (binds "a")
|
|
163
|
+
import a.b as c → module="a.b", name=None, alias="c"
|
|
164
|
+
from x.y import z → module="x.y", name="z"
|
|
165
|
+
from x import z as q → module="x", name="z", alias="q"
|
|
166
|
+
from . import m → module="", name="m", level=1
|
|
167
|
+
from ..utils import h → module="utils", name="h", level=2
|
|
168
|
+
from x import * → module="x", name="*", is_wildcard=True
|
|
169
|
+
"""
|
|
170
|
+
module: str
|
|
171
|
+
name: Optional[str] = None
|
|
172
|
+
alias: Optional[str] = None
|
|
173
|
+
level: int = 0 # number of leading dots for relative imports
|
|
174
|
+
is_wildcard: bool = False
|
|
175
|
+
is_static: bool = False
|
|
176
|
+
|
|
177
|
+
@property
|
|
178
|
+
def binding(self) -> Optional[str]:
|
|
179
|
+
"""Local name this import introduces (None for wildcard)."""
|
|
180
|
+
if self.is_wildcard:
|
|
181
|
+
return None
|
|
182
|
+
if self.alias:
|
|
183
|
+
return self.alias
|
|
184
|
+
if self.name:
|
|
185
|
+
return self.name
|
|
186
|
+
# plain `import a.b` binds the leftmost segment
|
|
187
|
+
return self.module.split(".")[0] if self.module else None
|
|
188
|
+
|
|
189
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
190
|
+
return {
|
|
191
|
+
"module": self.module,
|
|
192
|
+
"name": self.name,
|
|
193
|
+
"alias": self.alias,
|
|
194
|
+
"level": self.level,
|
|
195
|
+
"is_wildcard": self.is_wildcard,
|
|
196
|
+
"is_static": self.is_static,
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
@classmethod
|
|
200
|
+
def from_dict(cls, d: Dict[str, Any]) -> "ImportRecord":
|
|
201
|
+
return cls(
|
|
202
|
+
module=d.get("module", ""),
|
|
203
|
+
name=d.get("name"),
|
|
204
|
+
alias=d.get("alias"),
|
|
205
|
+
level=d.get("level", 0),
|
|
206
|
+
is_wildcard=d.get("is_wildcard", False),
|
|
207
|
+
is_static=d.get("is_static", False),
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
@dataclass
|
|
212
|
+
class Pattern:
|
|
213
|
+
category: PatternCategory
|
|
214
|
+
name: str
|
|
215
|
+
description: str
|
|
216
|
+
position: SourcePosition
|
|
217
|
+
file_path: Path
|
|
218
|
+
confidence: float = 0.5
|
|
219
|
+
symbol: Optional[Symbol] = None
|
|
220
|
+
metadata: Dict[str, Any] = field(default_factory=dict)
|
|
221
|
+
is_approved: bool = False
|
|
222
|
+
|
|
223
|
+
# Metadata keys that name the pattern's symbolic object, in priority
|
|
224
|
+
# order (same list the CLI scan table's Object column resolves from).
|
|
225
|
+
_HASH_OBJECT_KEYS = (
|
|
226
|
+
"handler", "function", "decorator", "name", "class",
|
|
227
|
+
"key", "key_name", "route_path", "module", "type", "role",
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
def compute_hash(self) -> str:
|
|
231
|
+
"""Structural identity hash (P2.4).
|
|
232
|
+
|
|
233
|
+
Identity is `category:name:anchor` where the anchor is the symbolic
|
|
234
|
+
object the pattern is about — the enclosing symbol (`Class.method`)
|
|
235
|
+
when present, else the most specific metadata object, else the
|
|
236
|
+
filename stem. Line numbers and directory paths never participate,
|
|
237
|
+
so a pattern keeps its identity (and approval status) when code
|
|
238
|
+
moves to a different line or the file is relocated.
|
|
239
|
+
"""
|
|
240
|
+
if self.symbol is not None:
|
|
241
|
+
anchor = (
|
|
242
|
+
f"{self.symbol.parent}.{self.symbol.name}"
|
|
243
|
+
if self.symbol.parent else self.symbol.name
|
|
244
|
+
)
|
|
245
|
+
else:
|
|
246
|
+
anchor = ""
|
|
247
|
+
for k in self._HASH_OBJECT_KEYS:
|
|
248
|
+
v = self.metadata.get(k)
|
|
249
|
+
if v:
|
|
250
|
+
anchor = str(v)
|
|
251
|
+
break
|
|
252
|
+
if not anchor:
|
|
253
|
+
anchor = Path(self.file_path).stem
|
|
254
|
+
key = f"{self.category.name}:{self.name}:{anchor}"
|
|
255
|
+
return hashlib.sha256(key.encode()).hexdigest()[:16]
|
|
256
|
+
|
|
257
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
258
|
+
return {
|
|
259
|
+
"category": self.category.name,
|
|
260
|
+
"name": self.name,
|
|
261
|
+
"description": self.description,
|
|
262
|
+
"position": self.position.to_dict(),
|
|
263
|
+
"file_path": str(self.file_path),
|
|
264
|
+
"confidence": self.confidence,
|
|
265
|
+
"metadata": self.metadata,
|
|
266
|
+
"is_approved": self.is_approved,
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
@classmethod
|
|
270
|
+
def from_dict(cls, d: Dict[str, Any]) -> "Pattern":
|
|
271
|
+
return cls(
|
|
272
|
+
category=PatternCategory[d["category"]],
|
|
273
|
+
name=d["name"],
|
|
274
|
+
description=d["description"],
|
|
275
|
+
position=SourcePosition.from_dict(d["position"]),
|
|
276
|
+
file_path=Path(d["file_path"]),
|
|
277
|
+
confidence=d.get("confidence", 0.5),
|
|
278
|
+
metadata=d.get("metadata", {}),
|
|
279
|
+
is_approved=d.get("is_approved", False),
|
|
280
|
+
)
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
@dataclass
|
|
284
|
+
class AnalysisResult:
|
|
285
|
+
file_path: Path
|
|
286
|
+
symbols: List[Symbol] = field(default_factory=list)
|
|
287
|
+
patterns: List[Pattern] = field(default_factory=list)
|
|
288
|
+
imports: List[str] = field(default_factory=list)
|
|
289
|
+
import_records: List[ImportRecord] = field(default_factory=list)
|
|
290
|
+
errors: List[str] = field(default_factory=list)
|
|
291
|
+
parse_time_ms: float = 0.0
|
|
292
|
+
language: str = "python"
|
|
293
|
+
module_name: Optional[str] = None
|
|
294
|
+
|
|
295
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
296
|
+
return {
|
|
297
|
+
"file_path": str(self.file_path),
|
|
298
|
+
"symbols": [s.to_dict() for s in self.symbols],
|
|
299
|
+
"patterns": [p.to_dict() for p in self.patterns],
|
|
300
|
+
"imports": self.imports,
|
|
301
|
+
"import_records": [r.to_dict() for r in self.import_records],
|
|
302
|
+
"errors": self.errors,
|
|
303
|
+
"parse_time_ms": self.parse_time_ms,
|
|
304
|
+
"language": self.language,
|
|
305
|
+
"module_name": self.module_name,
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
@classmethod
|
|
309
|
+
def from_dict(cls, d: Dict[str, Any]) -> "AnalysisResult":
|
|
310
|
+
return cls(
|
|
311
|
+
file_path=Path(d["file_path"]),
|
|
312
|
+
symbols=[Symbol.from_dict(s) for s in d.get("symbols", [])],
|
|
313
|
+
patterns=[Pattern.from_dict(p) for p in d.get("patterns", [])],
|
|
314
|
+
imports=d.get("imports", []),
|
|
315
|
+
import_records=[ImportRecord.from_dict(r) for r in d.get("import_records", [])],
|
|
316
|
+
errors=d.get("errors", []),
|
|
317
|
+
parse_time_ms=d.get("parse_time_ms", 0.0),
|
|
318
|
+
language=d.get("language", "python"),
|
|
319
|
+
module_name=d.get("module_name"),
|
|
320
|
+
)
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
@dataclass
|
|
324
|
+
class ProjectContext:
|
|
325
|
+
root_path: Path
|
|
326
|
+
files_analyzed: int = 0
|
|
327
|
+
total_symbols: int = 0
|
|
328
|
+
total_patterns: int = 0
|
|
329
|
+
frameworks: List[str] = field(default_factory=list)
|
|
330
|
+
naming_convention: Optional[str] = None
|
|
331
|
+
# Project-size baseline metrics (P3.15 follow-up).
|
|
332
|
+
lines_of_code: int = 0 # total physical lines across scanned files
|
|
333
|
+
non_blank_lines: int = 0 # non-empty / non-whitespace lines
|
|
334
|
+
total_functions: int = 0
|
|
335
|
+
total_classes: int = 0
|
|
336
|
+
total_variables: int = 0
|
|
337
|
+
total_modules: int = 0
|
|
338
|
+
entry_points: int = 0
|
|
339
|
+
calls_edges: int = 0
|
|
340
|
+
patterns_by_category: Dict[str, int] = field(default_factory=dict)
|
|
341
|
+
# Files that exist on disk but were correctly out of scope for this
|
|
342
|
+
# scan (a Maven module not in the active reactor, a resources
|
|
343
|
+
# directory), each `{"path", "reason", "detail"}` — see
|
|
344
|
+
# `JavaProjectConfig.excluded_source_files`. Empty for non-Java projects.
|
|
345
|
+
excluded_files: List[Dict[str, Any]] = field(default_factory=list)
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { existsSync } from 'node:fs';
|
|
4
|
+
import { dirname, join, resolve } from 'node:path';
|
|
5
|
+
import { homedir, constants } from 'node:os';
|
|
6
|
+
import { spawn } from 'node:child_process';
|
|
7
|
+
|
|
8
|
+
const args = process.argv.slice(2);
|
|
9
|
+
let projectArgument;
|
|
10
|
+
for (let i = 0; i < args.length; i++) {
|
|
11
|
+
if (args[i] === '-p' || args[i] === '--project') projectArgument = args[++i];
|
|
12
|
+
else if (args[i].startsWith('--project=')) projectArgument = args[i].slice('--project='.length);
|
|
13
|
+
}
|
|
14
|
+
const expand = path => path === '~' ? homedir() : path.startsWith('~/') || path.startsWith('~\\') ? join(homedir(), path.slice(2)) : path;
|
|
15
|
+
let project = resolve(expand(projectArgument || process.cwd()));
|
|
16
|
+
const executableFor = path => join(path, '.adl-toolkit', 'venv', process.platform === 'win32' ? 'Scripts' : 'bin', process.platform === 'win32' ? 'adl-drift.exe' : 'adl-drift');
|
|
17
|
+
while (!existsSync(executableFor(project)) && !projectArgument && dirname(project) !== project) project = dirname(project);
|
|
18
|
+
const executable = executableFor(project);
|
|
19
|
+
if (!existsSync(executable)) {
|
|
20
|
+
console.error('No managed ADL-Drift runtime found. Run adl-toolkit install in this project first.');
|
|
21
|
+
process.exitCode = 1;
|
|
22
|
+
} else {
|
|
23
|
+
const child = spawn(executable, projectArgument ? args : ['-p', project, ...args], { stdio: 'inherit' });
|
|
24
|
+
const interrupt = () => child.kill('SIGINT');
|
|
25
|
+
process.on('SIGINT', interrupt);
|
|
26
|
+
child.on('error', error => {
|
|
27
|
+
console.error(`Cannot run ADL-Drift: ${error.message}`);
|
|
28
|
+
process.exitCode = 1;
|
|
29
|
+
process.off('SIGINT', interrupt);
|
|
30
|
+
});
|
|
31
|
+
child.on('close', (code, signal) => {
|
|
32
|
+
process.off('SIGINT', interrupt);
|
|
33
|
+
process.exitCode = signal ? 128 + (constants.signals[signal] || 0) : code ?? 1;
|
|
34
|
+
});
|
|
35
|
+
}
|