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,488 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { constants as osConstants, homedir, tmpdir } from 'node:os';
4
+ import { closeSync, existsSync, mkdtempSync, openSync, readFileSync, realpathSync, writeSync } from 'node:fs';
5
+ import { dirname, join, resolve } from 'node:path';
6
+ import { fileURLToPath } from 'node:url';
7
+ import { spawn, spawnSync } from 'node:child_process';
8
+ import {
9
+ cancel,
10
+ confirm,
11
+ intro,
12
+ isCancel,
13
+ multiselect,
14
+ S_CHECKBOX_SELECTED,
15
+ S_CHECKBOX_INACTIVE,
16
+ S_RADIO_ACTIVE,
17
+ S_RADIO_INACTIVE,
18
+ note,
19
+ outro,
20
+ select,
21
+ text,
22
+ } from '@clack/prompts';
23
+
24
+ import { stripVTControlCharacters } from 'node:util';
25
+ import { SelectPrompt, MultiSelectPrompt, TextPrompt } from '@clack/core';
26
+
27
+ const root = resolve(dirname(fileURLToPath(import.meta.url)), '..');
28
+ const backend = join(root, 'scripts', 'install_toolkit.py');
29
+ const cancelled = Symbol('cancelled');
30
+ // Keep Clack's multi-selection behavior, with circles matching the graph menu.
31
+ const bulletOutput = new Proxy(process.stdout, {
32
+ get(stream, property) {
33
+ if (property === 'write') {
34
+ return (chunk, ...args) => stream.write(
35
+ typeof chunk === 'string'
36
+ ? chunk.replaceAll(S_CHECKBOX_SELECTED, S_RADIO_ACTIVE)
37
+ .replaceAll(S_CHECKBOX_INACTIVE, S_RADIO_INACTIVE)
38
+ : chunk,
39
+ ...args,
40
+ );
41
+ }
42
+ const value = Reflect.get(stream, property, stream);
43
+ return typeof value === 'function' ? value.bind(stream) : value;
44
+ },
45
+ });
46
+ const graphOptions = [
47
+ { value: 'adl-drift', label: 'ADL-Drift' },
48
+ { value: 'codegraph', label: 'CodeGraph' },
49
+ { value: 'gitnexus', label: 'Git Nexus' },
50
+ { value: 'graphify', label: 'Graphify' },
51
+ ];
52
+ const addonOptions = [
53
+ { value: 'caveman', label: 'Caveman', description: 'Concise output to save tokens.' },
54
+ { value: 'ponytail', label: 'Ponytail', description: 'Minimalist coding with less complexity.' },
55
+ ];
56
+ const clientOptions = [
57
+ { value: 'codex', label: 'Codex' },
58
+ { value: 'claude', label: 'Claude Code' },
59
+ { value: 'cursor', label: 'Cursor' },
60
+ { value: 'antigravity', label: 'Antigravity' },
61
+ ];
62
+
63
+ function help() {
64
+ console.log(`ADL Toolkit
65
+
66
+ Usage:
67
+ npx adl-toolkit install Open the interactive installer
68
+ npx adl-toolkit uninstall Open the interactive uninstaller
69
+ adl-toolkit install [options] Run without prompts
70
+ adl-toolkit uninstall [options] Run without prompts
71
+
72
+ Advanced commands:
73
+ adl-toolkit status Inspect the current project's installation
74
+ adl-toolkit doctor Check installation health
75
+ adl-toolkit repair Restore missing configuration and instructions
76
+ adl-toolkit update Upgrade the managed graph runtime
77
+
78
+ Install options:
79
+ --graph adl-drift|codegraph|gitnexus|graphify|other Select exactly one graph tool
80
+ --addon caveman|ponytail Add an integration (repeatable)
81
+ --client codex|claude|cursor|antigravity Select clients (repeatable; use all for every client)
82
+ --project PATH Target project (default: current directory)
83
+ --extra http|embeddings ADL optional dependency (repeatable)
84
+ --package SPEC Package used with --graph other
85
+ --no-index Skip initial indexing
86
+ --dry-run Show the plan without changing files
87
+
88
+ Uninstall options:
89
+ --project PATH Target project
90
+ --purge-index Also remove generated index data
91
+ --remove-client CLIENT Remove one managed client (repeatable)
92
+ --remove-addon caveman|ponytail Remove one managed integration (repeatable)
93
+ --yes Confirm removal without prompts
94
+ --dry-run Show the removal plan
95
+
96
+ Status, doctor, repair, and update options:
97
+ --project PATH Target project (default: current directory)
98
+ --dry-run Preview repair or update without changing files
99
+
100
+ Run with npx from this repository before npm publication:
101
+ npx --yes --package=. adl-toolkit install
102
+ npx --yes --package=. adl-toolkit uninstall`);
103
+ }
104
+
105
+ function chosen(value) {
106
+ if (isCancel(value)) throw cancelled;
107
+ return value;
108
+ }
109
+
110
+ function projectPath(value) {
111
+ if (value === '~') return homedir();
112
+ if (value.startsWith('~/') || value.startsWith('~\\')) {
113
+ return resolve(homedir(), value.slice(2));
114
+ }
115
+ return resolve(value);
116
+ }
117
+
118
+ function findPython() {
119
+ const configured = process.env.ADL_TOOLKIT_PYTHON;
120
+ const candidates = configured
121
+ ? [[configured, []]]
122
+ : process.platform === 'win32'
123
+ ? [['py', ['-3']], ['python', []], ['python3', []]]
124
+ : [['python3', []], ['python', []]];
125
+ for (const [command, prefix] of candidates) {
126
+ const probe = spawnSync(command, [...prefix, '--version'], { encoding: 'utf8' });
127
+ const match = `${probe.stdout || ''}${probe.stderr || ''}`.match(/Python (\d+)\.(\d+)/);
128
+ if (probe.status === 0 && match &&
129
+ (Number(match[1]) > 3 || Number(match[1]) === 3 && Number(match[2]) >= 10)) {
130
+ return [command, prefix];
131
+ }
132
+ }
133
+ throw new Error('Python 3.10 or newer is required. Install Python or set ADL_TOOLKIT_PYTHON.');
134
+ }
135
+
136
+ function runBackend(args) {
137
+ const [python, prefix] = findPython();
138
+ const result = spawnSync(python, [...prefix, backend, ...args], {
139
+ stdio: 'inherit', env: { ...process.env, PYTHONUTF8: '1', PYTHONIOENCODING: 'utf-8' },
140
+ });
141
+ if (result.error) throw result.error;
142
+ if (result.signal) return 128 + (osConstants.signals[result.signal] || 0);
143
+ return result.status ?? 1;
144
+ }
145
+
146
+ export async function runInstallation(args, output = process.stdout) {
147
+ const [python, prefix] = findPython();
148
+ const removing = args[0] === 'uninstall';
149
+ const action = removing ? 'Uninstall' : 'Installation';
150
+ const logPath = join(mkdtempSync(join(tmpdir(), `adl-toolkit-${removing ? 'uninstall' : 'install'}-`)), 'audit.log');
151
+ const log = openSync(logPath, 'wx', 0o600);
152
+ const value = flag => args.includes(flag) ? args[args.indexOf(flag) + 1] : undefined;
153
+ const choices = flag => args.flatMap((argument, i) => argument === flag ? [args[i + 1]] : []);
154
+ const manifest = removing ? readManifest(projectPath(value('--project') || process.cwd())) : null;
155
+ const summary = removing ? [
156
+ `Project: ${value('--project') || process.cwd()}`,
157
+ `Graph: ${manifest?.graph || value('--graph') || 'Managed installation'}`,
158
+ `Remove AI Agents: ${choices('--remove-client').join(', ') || 'None selected individually'}`,
159
+ `Remove plugins: ${choices('--remove-addon').join(', ') || 'None selected individually'}`,
160
+ `Scope: ${args.includes('--remove-client') || args.includes('--remove-addon') ? 'Selected components' : 'Entire installation'}`,
161
+ `Remove index: ${args.includes('--purge-index') ? 'Yes' : 'No'}`,
162
+ ] : [
163
+ `Graph: ${value('--graph')}`, `Plugins: ${choices('--addon').join(', ') || 'None'}`,
164
+ `AI Agents: ${choices('--client').join(', ')}`, `Project: ${value('--project')}`,
165
+ ...(args.includes('--package') ? [`Package: ${value('--package')}`] : []),
166
+ `Build index: ${args.includes('--no-index') ? 'No' : 'Yes'}`,
167
+ ];
168
+ const lines = [];
169
+ let pending = '', child, outputError;
170
+ const render = () => {
171
+ const width = Math.max(1, (output.columns || 80) - 4);
172
+ const height = Math.max(1, Math.min(8, (output.rows || 24) - summary.length - 7));
173
+ const visible = [...lines, ...(pending ? [pending] : [])].slice(-height);
174
+ const clip = line => Array.from(line).slice(0, width).join('');
175
+ output.write('\x1b[H\x1b[2J' + ['Your selections', ...summary, '',
176
+ `${action} progress · Ctrl+C Cancel`, '┌' + '─'.repeat(width + 2) + '┐',
177
+ ...visible.map(line => `│ ${clip(line).padEnd(width)} │`), '└' + '─'.repeat(width + 2) + '┘',
178
+ `Full audit: ${logPath}`].join('\n'));
179
+ };
180
+ const interrupt = () => child?.kill('SIGINT');
181
+ let timer;
182
+ try {
183
+ output.write('\x1b[?1049h\x1b[?25l');
184
+ render();
185
+ child = spawn(python, [...prefix, '-u', backend, ...args], {
186
+ stdio: ['ignore', 'pipe', 'pipe'],
187
+ env: { ...process.env, PYTHONUNBUFFERED: '1', PYTHONUTF8: '1', PYTHONIOENCODING: 'utf-8' },
188
+ });
189
+ process.on('SIGINT', interrupt);
190
+ for (const stream of [child.stdout, child.stderr]) {
191
+ stream.setEncoding('utf8');
192
+ stream.on('data', chunk => {
193
+ try {
194
+ writeSync(log, chunk);
195
+ const parts = (pending + stripVTControlCharacters(chunk)).split(/\r\n|[\r\n]/);
196
+ pending = parts.pop().slice(-2000);
197
+ lines.push(...parts.map(line => line.slice(-2000)));
198
+ lines.splice(0, Math.max(0, lines.length - 8));
199
+ } catch (error) {
200
+ outputError = error;
201
+ child.kill('SIGINT');
202
+ }
203
+ });
204
+ }
205
+ timer = setInterval(render, 100);
206
+ const status = await new Promise((resolve, reject) => {
207
+ child.once('error', reject);
208
+ child.once('close', (code, signal) => resolve(signal ? 128 + (osConstants.signals[signal] || 0) : code ?? 1));
209
+ });
210
+ if (outputError) throw outputError;
211
+ return status;
212
+ } finally {
213
+ clearInterval(timer);
214
+ process.off('SIGINT', interrupt);
215
+ closeSync(log);
216
+ output.write('\x1b[?25h\x1b[?1049l');
217
+ output.write(`\n${[...lines, pending].filter(Boolean).join('\n')}\nFull ${action.toLowerCase()} audit: ${logPath}\n`);
218
+ }
219
+ }
220
+
221
+ export async function wizardPrompt(kind, options) {
222
+ const Base = kind === 'text' ? TextPrompt : kind === 'multiselect' ? MultiSelectPrompt : SelectPrompt;
223
+ let direction = 'next';
224
+ class NavigablePrompt extends Base {
225
+ emit(event, ...args) {
226
+ // Keep horizontal arrows from changing menu selections; Left returns a step.
227
+ if (event === 'cursor' && ['left', 'right'].includes(args[0])) return;
228
+ return super.emit(event, ...args);
229
+ }
230
+ }
231
+ const validate = value => options.required && !value?.length
232
+ ? 'Choose at least one option' : options.validate?.(value);
233
+ const prompt = new NavigablePrompt({
234
+ ...options,
235
+ initialUserInput: kind === 'text' ? options.initialValue : undefined,
236
+ validate,
237
+ render() {
238
+ const heading = `${this.state === 'submit' ? '◇' : '◆'} ${options.message}`;
239
+ if (this.state === 'submit' || this.state === 'cancel') return heading;
240
+ const body = kind === 'text' ? `│ ${this.userInput || options.placeholder || ''}`
241
+ : options.options.map((option, i) => {
242
+ const selected = kind === 'multiselect' ? this.value?.includes(option.value) : this.value === option.value;
243
+ return `│ ${i === this.cursor ? '›' : ' '} ${selected ? '●' : '○'} ${option.label}`;
244
+ }).join('\n');
245
+ const hint = options.hint ? `\n│ ${options.hint}\n│` : '';
246
+ const description = kind !== 'text' && options.options[this.cursor]?.description;
247
+ const details = description ? `\n│\n│ \x1b[2m${description}\x1b[22m` : '';
248
+ return `${heading}${hint}\n${body}${details}${this.state === 'error' ? `\n│ ${this.error}` : ''}`;
249
+ },
250
+ });
251
+ prompt.on('key', (_char, key) => {
252
+ if (key.name !== 'left') return;
253
+ direction = 'back';
254
+ prompt.state = 'submit';
255
+ });
256
+ return { value: chosen(await prompt.prompt()), direction };
257
+ }
258
+
259
+ export async function installWizard(dryRun, prompts = { intro, note, ask: wizardPrompt }, output = process.stdout) {
260
+ const { intro, note, ask } = prompts;
261
+ let installationSummary;
262
+ // Use a separate screen so redraws never erase the user's terminal history.
263
+ if (output.isTTY) output.write('\x1b[?1049h');
264
+ try {
265
+ const values = { graph: 'adl-drift', addons: [], clients: [], project: process.cwd(), package: '', index: true };
266
+ const selected = new Set();
267
+ let position = 0;
268
+ while (true) {
269
+ if (output.isTTY) output.write('\x1b[H\x1b[2J');
270
+ intro('ADL Toolkit');
271
+ note('↑/↓ Choose · Space Toggle · ← Previous step · Enter Save and advance · Ctrl+C Cancel', 'Keyboard controls');
272
+ const steps = ['graph', 'addons', 'clients', 'project', values.graph === 'other' ? 'package' : 'index', 'review'];
273
+ const step = steps[position];
274
+ const fields = {
275
+ graph: ['select', { message: 'Choose one graph tool', options: graphOptions, initialValue: values.graph }],
276
+ addons: ['multiselect', { message: 'Choose saving token plugins', hint: 'Selected plugins are active by default in AGENTS.md. Disable them by asking the agent not to use them, or by editing the file.', options: addonOptions, initialValues: values.addons }],
277
+ clients: ['multiselect', { message: 'Choose one or more AI Agents', options: [{ value: 'all', label: 'Select all' }, ...clientOptions], initialValues: values.clients, required: true }],
278
+ project: ['text', { message: 'Project directory', initialValue: values.project, validate: value => value?.trim() ? undefined : 'Enter a project directory' }],
279
+ package: ['text', { message: 'Python package name and version', initialValue: values.package, placeholder: 'package-name==1.2.3', validate: value => value?.trim() ? undefined : 'Enter a package name' }],
280
+ index: ['select', { message: 'Build the graph index after installation?', options: [{ value: true, label: 'Yes (recommended)' }, { value: false, label: 'No (build manually later)' }], initialValue: values.index }],
281
+ review: ['select', { message: dryRun ? 'Show this installation plan?' : 'Install now?', options: [{ value: true, label: 'Yes' }, { value: false, label: 'Cancel' }], initialValue: true }],
282
+ };
283
+ if (step === 'index') note('Indexes your project so the graph tool can traverse code relationships. Recommended for immediate use; larger projects may take longer.', 'Graph index');
284
+ const labels = options => choices => choices.map(value => options.find(option => option.value === value)?.label || value).join(', ');
285
+ const clients = values.clients.includes('all') ? clientOptions.map(option => option.value) : values.clients;
286
+ const summary = [
287
+ ...(selected.has('graph') ? [`Graph: ${labels(graphOptions)([values.graph])}`] : []),
288
+ ...(selected.has('addons') ? [`Plugins: ${labels(addonOptions)(values.addons) || 'None'}`] : []),
289
+ ...(selected.has('clients') ? [`AI Agents: ${labels(clientOptions)(clients)}`] : []),
290
+ ...(selected.has('project') ? [`Project: ${projectPath(values.project)}`] : []),
291
+ ...(values.graph === 'other'
292
+ ? selected.has('package') ? [`Package: ${values.package}`] : []
293
+ : selected.has('index') ? [`Build index: ${values.index ? 'Yes' : 'No'}`] : []),
294
+ ].join('\n');
295
+ if (summary) note(summary, step === 'review' ? (dryRun ? 'Dry run' : 'Ready to install') : 'Your selections');
296
+ const { value, direction } = await ask(...fields[step]);
297
+ if (step !== 'review') values[step] = value;
298
+ if (direction === 'back') {
299
+ position = Math.max(0, position - 1);
300
+ continue;
301
+ }
302
+ if (step !== 'review') {
303
+ selected.add(step);
304
+ position++;
305
+ continue;
306
+ }
307
+ if (!value) throw cancelled;
308
+ installationSummary = summary;
309
+ const args = ['install', '--graph', values.graph, '--project', projectPath(values.project)];
310
+ for (const client of clients) args.push('--client', client);
311
+ for (const addon of values.addons) args.push('--addon', addon);
312
+ if (values.graph === 'other') args.push('--package', values.package.trim());
313
+ if (values.graph === 'other' || !values.index) args.push('--no-index');
314
+ if (dryRun) args.push('--dry-run');
315
+ return args;
316
+ }
317
+ } finally {
318
+ if (output.isTTY) output.write('\x1b[?1049l');
319
+ if (installationSummary) note(installationSummary, 'Installation selections');
320
+ }
321
+ }
322
+
323
+ function readManifest(project) {
324
+ const path = join(project, '.adl-toolkit', 'install.json');
325
+ if (!existsSync(path)) return null;
326
+ try {
327
+ const manifest = JSON.parse(readFileSync(path, 'utf8'));
328
+ const graphs = [...graphOptions.map(option => option.value), 'other'];
329
+ const clients = clientOptions.map(option => option.value);
330
+ const addons = addonOptions.map(option => option.value);
331
+ if (!manifest || !graphs.includes(manifest.graph) || !clients.includes(manifest.client) ||
332
+ (manifest.clients !== undefined && (!Array.isArray(manifest.clients) || !manifest.clients.length ||
333
+ manifest.clients.some(client => !clients.includes(client)))) ||
334
+ !Array.isArray(manifest.addons) || manifest.addons.some(addon => !addons.includes(addon))) {
335
+ return null;
336
+ }
337
+ return manifest;
338
+ } catch {
339
+ return null;
340
+ }
341
+ }
342
+
343
+ export async function uninstallWizard(dryRun, prompts = { intro, note, ask: wizardPrompt }, output = process.stdout) {
344
+ const { intro, note, ask } = prompts;
345
+ const values = { project: process.cwd(), removal: 'all', clients: [], addons: [], graph: 'adl-drift', client: 'codex', purge: false };
346
+ const selected = new Set();
347
+ let position = 0, removalSummary;
348
+ if (output.isTTY) output.write('\x1b[?1049h');
349
+ try {
350
+ while (true) {
351
+ if (output.isTTY) output.write('\x1b[H\x1b[2J');
352
+ intro('ADL Toolkit — Uninstall');
353
+ note('↑/↓ Choose · Space Toggle · ← Previous step · Enter Save and advance · Ctrl+C Cancel', 'Keyboard controls');
354
+ const project = projectPath(values.project);
355
+ const manifest = readManifest(project);
356
+ const installedClients = manifest?.clients || (manifest ? [manifest.client] : []);
357
+ const selective = manifest && values.removal !== 'all';
358
+ const steps = manifest
359
+ ? ['project', 'removal', ...(selective ? [values.removal === 'client' ? 'clients' : 'addons'] : ['purge']), 'review']
360
+ : ['project', 'graph', 'client', 'purge', 'review'];
361
+ const step = steps[position];
362
+ const removalOptions = [
363
+ { value: 'all', label: 'Entire managed installation' },
364
+ ...(installedClients.length > 1 ? [{ value: 'client', label: 'Selected AI Agents (keep graph runtime)' }] : []),
365
+ ...(manifest?.addons.length ? [{ value: 'addon', label: 'Selected plugins (keep graph runtime)' }] : []),
366
+ ];
367
+ const summary = [
368
+ `Project: ${project}`,
369
+ ...(manifest ? [
370
+ `Graph: ${graphOptions.find(option => option.value === manifest.graph)?.label}`,
371
+ `Installed plugins: ${manifest.addons.map(value => addonOptions.find(option => option.value === value)?.label).join(', ') || 'None'}`,
372
+ `Installed AI Agents: ${installedClients.map(value => clientOptions.find(option => option.value === value)?.label).join(', ')}`,
373
+ ...(selected.has('removal') ? [`Remove: ${removalOptions.find(option => option.value === values.removal)?.label}`] : []),
374
+ ...(values.removal === 'client' ? [`Remove AI Agents: ${values.clients.join(', ') || 'Not selected'}`] : []),
375
+ ...(values.removal === 'addon' ? [`Remove plugins: ${values.addons.join(', ') || 'Not selected'}`] : []),
376
+ ] : [`Graph: ${values.graph}`, `AI Agent: ${values.client}`]),
377
+ ...(!selective && selected.has('purge') ? [`Remove index: ${values.purge ? 'Yes' : 'No'}`] : []),
378
+ ].join('\n');
379
+ note(summary, 'Your selections');
380
+ if (!manifest && step !== 'project') note('No install manifest found. Select the earlier installation. Legacy skills have no ownership record; remove them explicitly with the Skills CLI.', 'Legacy installation');
381
+ const fields = {
382
+ project: ['text', { message: 'Project directory', initialValue: values.project, validate: value => value?.trim() ? undefined : 'Enter a project directory' }],
383
+ removal: ['select', { message: 'What would you like to remove?', options: removalOptions, initialValue: values.removal }],
384
+ clients: ['multiselect', { message: 'Choose AI Agents to remove', options: clientOptions.filter(option => installedClients.includes(option.value)), initialValues: values.clients, required: true,
385
+ validate: value => value?.length === installedClients.length ? 'Keep at least one AI Agent, or go back and remove the entire installation.' : undefined }],
386
+ addons: ['multiselect', { message: 'Choose plugins to remove', options: addonOptions.filter(option => manifest?.addons.includes(option.value)), initialValues: values.addons, required: true }],
387
+ graph: ['select', { message: 'Graph tool to remove', options: graphOptions, initialValue: values.graph }],
388
+ client: ['select', { message: 'Choose an AI Agent', options: clientOptions, initialValue: values.client }],
389
+ purge: ['select', { message: 'Also remove generated index data?', options: [{ value: false, label: 'No' }, { value: true, label: 'Yes' }], initialValue: values.purge }],
390
+ review: ['select', { message: dryRun ? 'Show this removal plan?' : 'Remove the selected components?', options: [{ value: false, label: 'Cancel' }, { value: true, label: dryRun ? 'Show plan' : 'Remove' }], initialValue: false }],
391
+ };
392
+ const { value, direction } = await ask(...fields[step]);
393
+ if (step !== 'review') values[step] = value;
394
+ if (step === 'project' && projectPath(values.project) !== project) {
395
+ selected.clear();
396
+ values.removal = 'all';
397
+ values.clients = [];
398
+ values.addons = [];
399
+ values.purge = false;
400
+ }
401
+ if (direction === 'back') {
402
+ position = Math.max(0, position - 1);
403
+ continue;
404
+ }
405
+ if (step !== 'review') { selected.add(step); position++; continue; }
406
+ if (!value) throw cancelled;
407
+ const args = ['uninstall', '--project', project];
408
+ if (!manifest) args.push('--graph', values.graph, '--client', values.client);
409
+ if (manifest && values.removal === 'client') for (const client of values.clients) args.push('--remove-client', client);
410
+ if (manifest && values.removal === 'addon') for (const addon of values.addons) args.push('--remove-addon', addon);
411
+ if (!selective && values.purge) args.push('--purge-index');
412
+ if (dryRun) args.push('--dry-run');
413
+ args.push('--yes');
414
+ removalSummary = summary;
415
+ return args;
416
+ }
417
+ } finally {
418
+ if (output.isTTY) output.write('\x1b[?1049l');
419
+ if (removalSummary) note(removalSummary, 'Removal selections');
420
+ }
421
+ }
422
+
423
+ async function main(argv) {
424
+ if (argv.includes('--help') || argv.includes('-h')) {
425
+ help();
426
+ return 0;
427
+ }
428
+ if (argv.includes('--version') || argv.includes('-v')) {
429
+ console.log('0.1.1');
430
+ return 0;
431
+ }
432
+ const first = argv[0];
433
+ const command = !first || first.startsWith('-') ? 'install' : first;
434
+ if (!['install', 'uninstall', 'status', 'doctor', 'repair', 'update'].includes(command)) {
435
+ console.error(`Unknown command: ${command}\n`);
436
+ help();
437
+ return 2;
438
+ }
439
+ const rest = first === command ? argv.slice(1) : argv;
440
+ if (['status', 'doctor', 'repair', 'update'].includes(command)) {
441
+ return runBackend([command, ...rest]);
442
+ }
443
+ const onlyDryRun = rest.length === 1 && rest[0] === '--dry-run';
444
+ if (rest.length && !onlyDryRun) {
445
+ const args = [command, ...rest];
446
+ return ['install', 'uninstall'].includes(command) && process.stdout.isTTY && !rest.includes('--dry-run')
447
+ && (command === 'install' ? rest.includes('--graph') : rest.includes('--yes'))
448
+ ? await runInstallation(args) : runBackend(args);
449
+ }
450
+ if (!process.stdin.isTTY || !process.stdout.isTTY) {
451
+ console.error('Interactive selection needs a terminal. Pass --graph and the other options explicitly.\n');
452
+ help();
453
+ return 2;
454
+ }
455
+ try {
456
+ const args = command === 'install'
457
+ ? await installWizard(onlyDryRun)
458
+ : await uninstallWizard(onlyDryRun);
459
+ const status = !onlyDryRun
460
+ ? await runInstallation(args)
461
+ : runBackend(args);
462
+ if (status === 0) {
463
+ outro(onlyDryRun
464
+ ? command === 'install' ? 'Installation plan shown.' : 'Uninstall plan shown.'
465
+ : command === 'install' ? 'Installation complete.'
466
+ : args.includes('--remove-client') || args.includes('--remove-addon')
467
+ ? 'Selected components removed. Shared graph runtime retained.'
468
+ : readManifest(args[args.indexOf('--project') + 1])
469
+ ? 'Some edited files were preserved. Review the cleanup notes above.'
470
+ : 'Uninstall complete.');
471
+ }
472
+ return status;
473
+ } catch (error) {
474
+ if (error === cancelled) {
475
+ cancel('Cancelled. No changes were made.');
476
+ return 0;
477
+ }
478
+ throw error;
479
+ }
480
+ }
481
+
482
+ if (process.argv[1] && realpathSync(process.argv[1]) === realpathSync(fileURLToPath(import.meta.url))) main(process.argv.slice(2)).then(
483
+ status => { process.exitCode = status; },
484
+ error => {
485
+ console.error(`Error: ${error.message}`);
486
+ process.exitCode = 1;
487
+ },
488
+ );
package/package.json CHANGED
@@ -1,6 +1,31 @@
1
1
  {
2
2
  "name": "adl-toolkit",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.1",
4
+ "description": "Interactive installer for one graph tool and optional agent integrations",
5
+ "type": "module",
6
+ "bin": {
7
+ "adl-toolkit": "bin/adl-toolkit.mjs",
8
+ "adl-drift": "bin/adl-drift.mjs"
9
+ },
10
+ "files": [
11
+ "bin/",
12
+ "adl_drift/**/*.py",
13
+ "adl_drift/harness/skills/",
14
+ "adl_drift/server/static/",
15
+ "scripts/install_toolkit.py",
16
+ "pyproject.toml",
17
+ "README.md",
18
+ "INSTALL_TOOLKIT.md"
19
+ ],
20
+ "scripts": {
21
+ "test": "node --test tests/toolkit-cli.test.mjs"
22
+ },
23
+ "engines": {
24
+ "node": ">=22.20.0"
25
+ },
26
+ "dependencies": {
27
+ "@clack/prompts": "1.4.0",
28
+ "@clack/core": "1.3.1"
29
+ },
30
+ "license": "MIT"
31
+ }
package/pyproject.toml ADDED
@@ -0,0 +1,49 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "adl-drift"
7
+ version = "0.1.0"
8
+ description = "Static analysis engine for Python and Java with an MCP server."
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = { text = "MIT" }
12
+ authors = [{ name = "ADL Drift" }]
13
+ dependencies = [
14
+ "tree-sitter>=0.23",
15
+ "tree-sitter-python>=0.23",
16
+ "tree-sitter-java>=0.23",
17
+ "defusedxml>=0.7.1",
18
+ "networkx>=3.2",
19
+ "click>=8.1",
20
+ "rich>=13.7",
21
+ "pydantic>=2.6",
22
+ "platformdirs>=4.2",
23
+ "mcp>=1.2,<2",
24
+ ]
25
+
26
+ [project.optional-dependencies]
27
+ http = ["fastapi>=0.110", "uvicorn>=0.27", "sse-starlette>=2.0"]
28
+ embeddings = ["sentence-transformers>=2.6", "numpy>=1.26"]
29
+ dev = ["pytest>=8.0", "pytest-asyncio>=0.23"]
30
+ test = [
31
+ "pytest>=8.0",
32
+ "pytest-asyncio>=0.23",
33
+ "fastapi>=0.110",
34
+ "httpx>=0.27",
35
+ "sse-starlette>=2.0",
36
+ "numpy>=1.26",
37
+ ]
38
+
39
+ [project.scripts]
40
+ adl-drift = "adl_drift.cli.main:main"
41
+ adl-drift-mcp = "adl_drift.server.mcp_stdio:cli_entry"
42
+
43
+ [tool.setuptools.packages.find]
44
+ include = ["adl_drift*"]
45
+ exclude = ["tests*"]
46
+
47
+ [tool.setuptools.package-data]
48
+ "adl_drift.harness" = ["skills/*.md"]
49
+ "adl_drift.server" = ["static/*.html"]