adl-toolkit 0.0.0-stage → 0.1.3

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/adl-drift.md +25 -0
  36. package/adl_drift/harness/skills/drift-antigravity.md +19 -0
  37. package/adl_drift/harness/skills/drift-generic.md +19 -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,163 @@
1
+ # ADL Toolkit — User Guide
2
+
3
+ ADL Toolkit connects a code graph tool to your AI assistants. Choose ADL-Drift,
4
+ CodeGraph, Git Nexus, or Graphify, and optionally add Caveman and Ponytail.
5
+
6
+ Use two commands to manage your setup:
7
+
8
+ ```bash
9
+ npx adl-toolkit install
10
+ npx adl-toolkit uninstall
11
+ ```
12
+
13
+ Follow the steps below to install the toolkit, start using it, and remove it when needed.
14
+
15
+ ## First-time checklist
16
+
17
+ Before you start, install:
18
+
19
+ - Node.js **22.20+ within the 22.x series, or 24.11+**.
20
+ - npm and npx, included with Node.js.
21
+ - Python **3.11+** and Git.
22
+ - At least one supported AI assistant: Codex, Claude Code, Cursor, or Antigravity.
23
+
24
+ Open a terminal and check:
25
+
26
+ ```bash
27
+ node --version
28
+ npm --version
29
+ git --version
30
+ ```
31
+
32
+ Check Python with `python3 --version` on Linux, macOS, or WSL, or
33
+ `py -3 --version` / `python --version` on Windows.
34
+ On Windows, use `npm.cmd` and `npx.cmd` if PowerShell blocks npm or npx scripts.
35
+ After installing or upgrading a prerequisite, reopen your terminal.
36
+
37
+ ## 1. Install
38
+
39
+ Open a terminal in the project you want your AI assistant to explore, then run:
40
+
41
+ ```bash
42
+ npx adl-toolkit install
43
+ ```
44
+
45
+ If npm asks to download `adl-toolkit`, confirm to continue.
46
+
47
+ ### Linux and macOS
48
+
49
+ ```bash
50
+ cd /path/to/my-project
51
+ npx adl-toolkit install
52
+ ```
53
+
54
+ ### Running on Windows (PowerShell or CMD)
55
+
56
+ Use Windows Node.js and Python:
57
+
58
+ ```powershell
59
+ cd "C:\work\my-project"
60
+ npx.cmd adl-toolkit install
61
+ ```
62
+
63
+ In CMD, use `cd /d` when changing drives.
64
+
65
+ ### Running from WSL
66
+
67
+ Use Linux Node.js, npm, and Python inside WSL when your AI assistant also runs there:
68
+
69
+ ```bash
70
+ cd /mnt/e/work/my-project
71
+ npx adl-toolkit install
72
+ ```
73
+
74
+ If your assistant runs on Windows, follow the Windows instructions instead.
75
+ For mixed Windows/Linux tool errors, see [WSL recovery](windows_installation.md#recovery-in-wsl).
76
+
77
+ ### Choose your setup
78
+
79
+ The installer walks you through these choices:
80
+
81
+ 1. **Graph tool:** select ADL-Drift, CodeGraph, Git Nexus, or Graphify.
82
+ 2. **Optional plugins:** select Caveman for concise agent responses, Ponytail for minimal coding changes, both, or neither.
83
+ 3. **AI assistants:** select one or more assistants, or choose **Select all**.
84
+ 4. **Project directory:** confirm the project you want to analyze.
85
+ 5. **Graph index:** keep **Yes (recommended)** to build the index during installation. Large projects may take longer.
86
+ 6. **Review:** check your selections and confirm **Install now?**.
87
+
88
+ Use **↑/↓** to move, **Space** to toggle selections, **Enter** to save and continue,
89
+ **←** to return to the previous step, and **Ctrl+C** to cancel.
90
+
91
+ Wait for **Installation complete.** Follow any next steps printed by the installer.
92
+ Reopen your terminal if it reports PATH changes, then restart your selected AI assistants.
93
+
94
+ ## 2. Use the toolkit
95
+
96
+ Open the same project in one of the assistants you selected during installation.
97
+ Start a new conversation and ask it to use your selected graph tool. For example:
98
+
99
+ > Use the code graph to explain this project's main modules and entry points.
100
+
101
+ > Find the callers of this function and show which parts of the project a change could affect.
102
+
103
+ If you skipped indexing, run the graph build command printed after installation first.
104
+ To refresh the index after changing your code, use that tool's graph build command again.
105
+
106
+ Selected Caveman and Ponytail plugins are activated through the project's agent instructions.
107
+ To disable one, ask your assistant not to use it, or remove it through the uninstall menu.
108
+
109
+ ## 3. Uninstall
110
+
111
+ Open a terminal in the project where you installed the toolkit:
112
+
113
+ ```bash
114
+ npx adl-toolkit uninstall
115
+ ```
116
+
117
+ On Windows, use `npx.cmd adl-toolkit uninstall`.
118
+
119
+ 1. Confirm the **Project directory**.
120
+ 2. Choose **Entire managed installation**, or remove selected plugins or AI assistant connections when those options are available.
121
+ 3. For a full uninstall, choose whether to remove generated index data. The default **No** keeps it.
122
+ 4. Review the summary and select **Remove** to confirm. **Cancel** is selected by default.
123
+
124
+ Removing selected plugins or assistant connections keeps the shared graph runtime.
125
+ To switch graph tools, uninstall the current managed installation, then run
126
+ `npx adl-toolkit install` and select the new tool.
127
+
128
+ If uninstall reports that a file was changed and retained, review the reported file
129
+ before retrying. If it cannot find your installation, check that you selected the original project directory.
130
+
131
+ ## Everyday commands
132
+
133
+ Install and uninstall are the main commands. For troubleshooting or maintenance,
134
+ run these advanced commands from your project directory:
135
+
136
+ ```bash
137
+ # Inspect installation files
138
+ npx adl-toolkit status
139
+
140
+ # Check installation health, including ADL-Drift's live MCP connection
141
+ npx adl-toolkit doctor
142
+
143
+ # Restore missing managed configuration and instructions
144
+ npx adl-toolkit repair
145
+
146
+ # Upgrade the managed graph runtime
147
+ npx adl-toolkit update
148
+
149
+ # Show all commands and options
150
+ npx adl-toolkit --help
151
+ ```
152
+
153
+ Update upgrades the runtime; it does not replace the graph build command used to refresh your index.
154
+ On Windows, use `npx.cmd` for these commands too.
155
+
156
+ ## If something fails
157
+
158
+ - **npm or npx is missing:** install or repair Node.js, then reopen your terminal.
159
+ - **Python cannot be found:** check its version and PATH. If needed, set `ADL_TOOLKIT_PYTHON` to your Python executable path.
160
+ - **An installation step fails:** open the full audit log at the path printed by the installer, then retry the reported command.
161
+ - **Your assistant cannot connect:** restart it in the installed project and run `npx adl-toolkit doctor`. For ADL-Drift, keep the failing checks and server traceback when reporting the issue.
162
+ - **Configuration is missing:** run `npx adl-toolkit repair`, then restart your assistant.
163
+ - **Windows or WSL cannot launch the command:** see [Windows recovery](windows_installation.md#recovery-in-native-windows) or [WSL recovery](windows_installation.md#recovery-in-wsl).
package/README.md CHANGED
@@ -1,3 +1,270 @@
1
- # Temporary Holding Version
1
+ # ADL Drift
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ ADL Drift indexes and analyzes Python and Java source code with a pure-Python runtime.
4
+
5
+ ## Quick Start
6
+
7
+ **Connect a graph tool to your AI assistants with ADL Toolkit.**
8
+ Check the [prerequisites](INSTALL_TOOLKIT.md#first-time-checklist),
9
+ then open a terminal in your project and run:
10
+
11
+ ```bash
12
+ npx adl-toolkit install
13
+ ```
14
+
15
+ Choose your graph tool, optional plugins, and AI assistants in the menu.
16
+ Confirm your project, build the index, then restart your assistants to start using the graph.
17
+
18
+ To remove the setup or selected components, run:
19
+
20
+ ```bash
21
+ npx adl-toolkit uninstall
22
+ ```
23
+
24
+ On Windows, use `npx.cmd`. Follow the [ADL Toolkit user guide](INSTALL_TOOLKIT.md)
25
+ for the full installation, usage, and uninstall walkthrough, including
26
+ [advanced commands](INSTALL_TOOLKIT.md#everyday-commands) and troubleshooting.
27
+
28
+ **Using ADL Drift from an AI agent (Claude Code / Cursor / Antigravity / Codex VS
29
+ Code)?**
30
+
31
+ For Claude, Cursor, or Antigravity:
32
+
33
+ ```bash
34
+ pip install -e . && adl-drift mcp setup
35
+ ```
36
+
37
+ Then verify with `adl-drift mcp doctor`. Full guide (harness vs MCP-only,
38
+ CI gate, watch mode, troubleshooting): **[SETUP.md](SETUP.md)**.
39
+
40
+ Codex VS Code is included: setup writes its trusted-project
41
+ `.codex/config.toml` entry. Use the full [VS Code demo](DEMO_HAPPYCASE_VSCODE.md)
42
+ for a walkthrough.
43
+
44
+ For a Java/JUnit 5 indexing demo, including the reproducible cold-scan and
45
+ incremental-correctness commands, see [DEMO_JAVA_JUNIT.md](DEMO_JAVA_JUNIT.md).
46
+
47
+ ```bash
48
+ # 1. Scan the project — this now also builds and persists the graph
49
+ adl-drift -p adl_drift scan adl_drift
50
+
51
+ # 2. Run graph stats (first time builds + stores)
52
+ adl-drift -p adl_drift graph
53
+
54
+ # 3. Run it again — should be instant because it loads from SQLite
55
+ adl-drift -p adl_drift graph
56
+
57
+ # 4. Impact queries are also cached
58
+ adl-drift -p adl_drift impact add_knowledge
59
+ ```
60
+
61
+ ## Command Reference
62
+
63
+ > Full plain-language reference with every option, output-column explanations, and workflows: **[COMMANDS.md](COMMANDS.md)**.
64
+
65
+ Global option (goes **before** the command): `--project, -p <dir>` — project root (default `.`). Example: `adl-drift -p adl_drift scan adl_drift`.
66
+
67
+ | Command | What it does | Options |
68
+ |---------|--------------|---------|
69
+ | `adl-drift scan [PATH]` | Scan files/directories for patterns; also builds + persists the call graph | `-c, --category <name>` filter to one category (e.g. `security`) · `-f, --format table\|json\|github` · `-i, --incremental` only re-parse changed files · `--more-info` beginner-friendly output (see below) · `--full` show all rows with `--more-info` |
70
+ | `adl-drift patterns` | List every stored pattern with its ID and approval state | — |
71
+ | `adl-drift approve <id>` | Mark a pattern as approved ("this is intentional, stop flagging it") | — |
72
+ | `adl-drift gate` | CI gate — exits 1 if unapproved patterns exist | `--json` machine contract: `{violations, new_patterns, scan_errors, high_security}`; exits 1 only on HIGH security findings or scan errors |
73
+ | `adl-drift graph` | Call-graph statistics: node/edge counts by type, entry points | — |
74
+ | `adl-drift impact <symbol>` | Blast radius: who calls `<symbol>`, directly and transitively. Accepts qualified (`pkg.mod.Class.method`) or simple names | `-f, --format tree\|table\|json` (default `tree`) · `--include-initializers` counts Java `<init>`/`<clinit>` contexts in caller totals |
75
+ | `adl-drift viz` | Interactive graph visualization in the browser (live-updates on file save) | `--host` (default `127.0.0.1`) · `--port` (default `8765`) |
76
+ | `adl-drift learn <topic> [text...]` | Store tribal knowledge (no text → opens `$EDITOR`, or pipe via stdin) | — |
77
+ | `adl-drift why <topic>` | Retrieve tribal knowledge for a topic | — |
78
+ | `adl-drift server` | MCP server over HTTP/SSE | `--port` (default `3000`) |
79
+ | `adl-drift mcp serve` | MCP server over stdio (Claude Code / Cursor / Claude Desktop / Antigravity / Codex VS Code) | — |
80
+ | `adl-drift mcp setup` | Auto-configure MCP clients: writes `.mcp.json` / `.cursor/mcp.json` / `.codex/config.toml` / `~/.gemini/config/mcp_config.json` + installs skill files (merge, never clobber) | `--claude` · `--cursor` · `--antigravity` · `--codex` · `--print` dry run |
81
+ | `adl-drift mcp doctor` | Verify wiring: config → interpreter → SDK → live stdio handshake → tool list | — |
82
+ | `adl-drift watch` | Keep the index fresh: hash-based polling + incremental rescans | `--interval <s>` (default 2) |
83
+
84
+ ### `scan --more-info` (easy mode)
85
+
86
+ Beginner-friendly report: summary line (`Scanned 59 files · 847 findings · 4 security (3 HIGH, 1 MEDIUM)`), per-category counts, a plain-language **What was found** column, color-coded **Severity**, confidence as words, and the first 50 rows with a footer hint (`--full` lifts the cap).
87
+
88
+ ```bash
89
+ adl-drift -p . scan . --more-info
90
+ adl-drift -p . scan . --more-info --full # all rows
91
+ adl-drift -p . scan . --more-info -c security # one category
92
+ ```
93
+
94
+ ## Reference: Categories, Types & Values
95
+
96
+ ### Pattern categories (the `Category` column in scan output)
97
+
98
+ | Category | What it covers |
99
+ |----------|----------------|
100
+ | `ROUTES` | Web endpoints from Flask/FastAPI/Django and Spring MVC/JAX-RS |
101
+ | `ERRORS` | Python error handling: broad/empty handlers and custom exception classes |
102
+ | `DATA_ACCESS` | Python ORM/raw SQL plus JDBC, JPA/Hibernate, Spring Data, and MyBatis operations |
103
+ | `DECORATORS` | Python decorators and Java annotations used for framework behavior |
104
+ | `ASYNC` | Python async/await patterns and Java asynchronous APIs |
105
+ | `IMPORTS` | Python and Java imports with language-appropriate classification |
106
+ | `SECURITY` | Risky execution, SQL construction, deserialization, crypto, reflection, secrets, and production assertions — carries a **Severity** |
107
+ | `TESTS` | pytest/unittest and JUnit/TestNG tests, fixtures, and lifecycle methods |
108
+ | `CONFIG` | Environment/config reads and constant conventions |
109
+ | `LOGGING` | Console and logger calls, including inefficient message construction |
110
+ | `TYPES` | Python annotation coverage and Java raw/nullability type conventions |
111
+
112
+ ### Scan table columns
113
+
114
+ | Column | Meaning |
115
+ |--------|---------|
116
+ | `Object` | The concrete thing the finding is about — the function, decorator, imported module, env-var key, etc. Empty when the finding has no single symbol (e.g. a bare `except:`) |
117
+ | `Severity` | Security/logging findings only: `HIGH` (red — fix or justify), `MEDIUM` (yellow — review), `LOW` (dim — informational) |
118
+ | `Confidence` | How sure the detector is. Numeric `0–1` in the default table; words in `--more-info`: **Certain** ≥ 0.85, **Likely** ≥ 0.6, **Guess** below |
119
+ | `Line` | 1-indexed line in the file |
120
+
121
+ ### Graph node types (`adl-drift graph`, viz UI)
122
+
123
+ | Node type | Meaning | Viz shape |
124
+ |-----------|---------|-----------|
125
+ | `MODULE` | One `.py` or `.java` file (`pkg.sub.mod`) | rounded box, dark |
126
+ | `CLASS` | Class definition | diamond, grey |
127
+ | `FUNCTION` | Function or method | dot, emerald |
128
+ | `VARIABLE` | Module-level constant or `self.X` attribute defined in `__init__` | dot |
129
+
130
+ Node IDs are dotted qualified names — `module.Class.method` — relative to the detected project root.
131
+
132
+ ### Graph edge types
133
+
134
+ | Edge | Meaning | Emitted confidence |
135
+ |------|---------|--------------------|
136
+ | `CONTAINS` | Module contains class/function; class contains methods | 1.0 |
137
+ | `CALLS` | Function A calls function B | 0.3–0.95 by resolution strategy (see below) |
138
+ | `AWAITS` | The call is awaited (`await f()`) — coexists with `CALLS` | same as the call |
139
+ | `IMPORTS` | Module imports another module | 1.0 |
140
+ | `INHERITS` | Class extends a base class | 1.0 |
141
+ | `DECORATED_BY` | Symbol wrapped by a decorator function | 1.0 |
142
+ | `REFERENCES` | Function reads a variable/constant (`return MAX_RETRIES`, `self.db`) | 0.85–0.95 |
143
+ | `TYPE_OF` | Function has a parameter annotated with a class (`def f(s: Service)`) | 1.0 |
144
+ | `RETURNS` | Function's return annotation is a class (`-> Service`) | 1.0 |
145
+
146
+ `CALLS` confidence by how the call was resolved: `0.95` `self.x()` on own class · `0.9` same-module name · `0.85` via import / inherited method / `super()` / type-annotated receiver · `0.75` chained call (`lib.Db.query()`) · `0.6` unique global name · `0.3–0.5` guessed by attribute name. The viz draws ≥ 0.85 solid, 0.6–0.85 dashed, < 0.6 faint.
147
+
148
+ **Blast radius** (`adl-drift impact`) follows incoming `CALLS`, `REFERENCES`, `TYPE_OF`, and `RETURNS` edges — so changing a constant reports every function that reads it, and changing a class reports every function annotated with it, plus their callers transitively. Synthetic Java field/initializer-block contexts (`<init>`/`<clinit>`) are reported in separate initializer fields by default; pass `--include-initializers` to include them in the ordinary caller totals.
149
+
150
+ ### Symbol kinds (internal `Symbol.kind`)
151
+
152
+ `function` (incl. methods — enclosing class recorded in `parent`) · `class` · `variable` (module-level assignment) · `attribute` (`self.X = ...` in `__init__`).
153
+
154
+ ## Node ID Collision & Project Root Detection
155
+
156
+ In large codebases and monorepos, multiple packages can contain files with the same relative path (e.g. `backend/api/users.py` and `frontend/api/users.py`). ADL Drift automatically detects your project root by looking for `pyproject.toml`, `.git`, `setup.py`, or `setup.cfg` and computes fully-qualified module names relative to that root. This prevents symbols from overwriting each other in the call graph.
157
+
158
+ ### How it works
159
+
160
+ - **Detection**: `CallGraphBuilder.find_project_root()` walks up the directory tree from the scan target until it finds a root marker.
161
+ - **Module naming**: `_path_to_module()` strips the project root prefix from every file path, so `/repo/backend/api/users.py` becomes `backend.api.users` instead of a truncated heuristic.
162
+ - **Fallback**: If no project root is found, ADL Drift falls back to the last 3 path components for backward compatibility.
163
+
164
+ ### Verifying the fix
165
+
166
+ Run the collision test to see the before/after behavior:
167
+
168
+ ```bash
169
+ pytest tests/test_graph.py::TestCallGraphBuilder::test_no_node_id_collision_with_project_root -v
170
+ ```
171
+
172
+ Or inspect the graph in a real project and confirm that nested packages produce distinct node IDs:
173
+
174
+ ```bash
175
+ adl-drift -p . graph
176
+ ```
177
+
178
+ ### Test scripts
179
+
180
+ These are the exact scripts used during development to verify the collision fix.
181
+
182
+ **1. Full collision demo (shows old vs new behavior side-by-side)**
183
+
184
+ ```python
185
+ from pathlib import Path
186
+ import tempfile
187
+ from adl_drift.parser.core import PythonParser
188
+ from adl_drift.parser.extractor import SymbolExtractor
189
+ from adl_drift.graph.builder import CallGraphBuilder
190
+
191
+ with tempfile.TemporaryDirectory() as td:
192
+ root = Path(td)
193
+ # Paths where the last 3 components are IDENTICAL — old code collides here
194
+ (root / 'services' / 'backend' / 'api' / 'v1').mkdir(parents=True)
195
+ (root / 'services' / 'frontend' / 'api' / 'v1').mkdir(parents=True)
196
+ (root / 'pyproject.toml').write_text('[project]\n')
197
+
198
+ (root / 'services' / 'backend' / 'api' / 'v1' / 'users.py').write_text('def list(): pass\n')
199
+ (root / 'services' / 'frontend' / 'api' / 'v1' / 'users.py').write_text('def list(): pass\n')
200
+
201
+ parser = PythonParser()
202
+ extractor = SymbolExtractor(parser)
203
+ r1 = extractor.extract(root / 'services' / 'backend' / 'api' / 'v1' / 'users.py')
204
+ r2 = extractor.extract(root / 'services' / 'frontend' / 'api' / 'v1' / 'users.py')
205
+
206
+ # OLD: last-3 heuristic collapses both files into api.v1.users
207
+ old = CallGraphBuilder(project_root=None)
208
+ old.add_file_result(r1)
209
+ old.add_file_result(r2)
210
+ print('OLD nodes:', sorted(old._nodes))
211
+ print('OLD count :', len(old._nodes), '(should be 4, is 2 -> COLLISION)')
212
+
213
+ # NEW: full relative path from project root
214
+ new = CallGraphBuilder(project_root=root)
215
+ new.add_file_result(r1)
216
+ new.add_file_result(r2)
217
+ print('NEW nodes:', sorted(new._nodes))
218
+ print('NEW count :', len(new._nodes), '(4 -> DISTINCT)')
219
+ ```
220
+
221
+ Expected output:
222
+
223
+ ```
224
+ OLD nodes: ['api.v1.users', 'api.v1.users.list']
225
+ OLD count : 2 (should be 4, is 2 -> COLLISION)
226
+ NEW nodes: ['services.backend.api.v1.users', 'services.backend.api.v1.users.list',
227
+ 'services.frontend.api.v1.users', 'services.frontend.api.v1.users.list']
228
+ NEW count : 4 (4 -> DISTINCT)
229
+ ```
230
+
231
+ **2. Quick live-project check**
232
+
233
+ ```python
234
+ from pathlib import Path
235
+ from adl_drift.cli.commands import DriftCommands
236
+
237
+ cmd = DriftCommands(Path('.'))
238
+ stats = cmd.graph_stats()
239
+ print('Nodes by type:', stats.get('node_types'))
240
+ print('Total nodes :', stats.get('total_nodes'))
241
+ ```
242
+
243
+ **3. Unit test (also in `tests/test_graph.py`)**
244
+
245
+ ```python
246
+ def test_no_node_id_collision_with_project_root(self, tmp_path):
247
+ """P0.2: Files with same relative path in different subtrees must have unique node IDs."""
248
+ backend = tmp_path / "backend" / "api"
249
+ frontend = tmp_path / "frontend" / "api"
250
+ backend.mkdir(parents=True)
251
+ frontend.mkdir(parents=True)
252
+
253
+ (backend / "users.py").write_text("def get_users(): pass\n")
254
+ (frontend / "users.py").write_text("def get_users(): pass\n")
255
+
256
+ (tmp_path / "pyproject.toml").write_text("[project]\n")
257
+
258
+ extractor = SymbolExtractor(PythonParser())
259
+ result1 = extractor.extract(backend / "users.py")
260
+ result2 = extractor.extract(frontend / "users.py")
261
+
262
+ builder = CallGraphBuilder(project_root=tmp_path)
263
+ builder.add_file_result(result1)
264
+ builder.add_file_result(result2)
265
+
266
+ assert "backend.api.users" in builder._nodes
267
+ assert "frontend.api.users" in builder._nodes
268
+ assert "backend.api.users.get_users" in builder._nodes
269
+ assert "frontend.api.users.get_users" in builder._nodes
270
+ ```
@@ -0,0 +1,6 @@
1
+ """
2
+ ADL Drift - Static Analysis Engine
3
+ A Python reimplementation of the Drift v2 architecture using tree-sitter.
4
+ """
5
+
6
+ __version__ = "0.1.0"
@@ -0,0 +1 @@
1
+ """Command-line interface."""