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,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.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
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"]
|