veefore 1.4.1 → 1.6.0
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/dist/veefore.mjs +1617 -247
- package/package.json +1 -1
package/dist/veefore.mjs
CHANGED
|
@@ -236,29 +236,29 @@ var ApiClient = class {
|
|
|
236
236
|
setToken(token) {
|
|
237
237
|
this.token = token;
|
|
238
238
|
}
|
|
239
|
-
async get(
|
|
239
|
+
async get(path14, query = {}) {
|
|
240
240
|
const search = new URLSearchParams();
|
|
241
241
|
for (const [key, value] of Object.entries(query)) {
|
|
242
242
|
if (value !== void 0) search.set(key, String(value));
|
|
243
243
|
}
|
|
244
244
|
const suffix = search.toString() === "" ? "" : `?${search.toString()}`;
|
|
245
|
-
return this.send("GET", `${
|
|
245
|
+
return this.send("GET", `${path14}${suffix}`);
|
|
246
246
|
}
|
|
247
|
-
async post(
|
|
248
|
-
return this.send("POST",
|
|
247
|
+
async post(path14, body) {
|
|
248
|
+
return this.send("POST", path14, body);
|
|
249
249
|
}
|
|
250
|
-
async patch(
|
|
251
|
-
return this.send("PATCH",
|
|
250
|
+
async patch(path14, body) {
|
|
251
|
+
return this.send("PATCH", path14, body);
|
|
252
252
|
}
|
|
253
|
-
async delete(
|
|
254
|
-
return this.send("DELETE",
|
|
253
|
+
async delete(path14) {
|
|
254
|
+
return this.send("DELETE", path14);
|
|
255
255
|
}
|
|
256
256
|
/** Raw request for endpoints that return a non-JSON body (exports). */
|
|
257
|
-
async raw(method,
|
|
258
|
-
return this.fetchWithTimeout(method,
|
|
257
|
+
async raw(method, path14, body) {
|
|
258
|
+
return this.fetchWithTimeout(method, path14, body);
|
|
259
259
|
}
|
|
260
|
-
async send(method,
|
|
261
|
-
const response = await this.fetchWithTimeout(method,
|
|
260
|
+
async send(method, path14, body) {
|
|
261
|
+
const response = await this.fetchWithTimeout(method, path14, body);
|
|
262
262
|
const text = await response.text();
|
|
263
263
|
const parsed = parseJson(text);
|
|
264
264
|
if (response.ok) {
|
|
@@ -266,14 +266,14 @@ var ApiClient = class {
|
|
|
266
266
|
if (parsed && "data" in parsed) return parsed.data;
|
|
267
267
|
throw new CliApiError(
|
|
268
268
|
"UNEXPECTED_RESPONSE",
|
|
269
|
-
`The control plane returned an unexpected body for ${method} ${
|
|
269
|
+
`The control plane returned an unexpected body for ${method} ${path14}.`,
|
|
270
270
|
response.status,
|
|
271
271
|
response.headers.get("x-request-id")
|
|
272
272
|
);
|
|
273
273
|
}
|
|
274
274
|
const error = new CliApiError(
|
|
275
275
|
parsed?.error?.code ?? `HTTP_${response.status}`,
|
|
276
|
-
parsed?.error?.message ?? `${method} ${
|
|
276
|
+
parsed?.error?.message ?? `${method} ${path14} failed with ${response.status} ${response.statusText}.`,
|
|
277
277
|
response.status,
|
|
278
278
|
parsed?.error?.request_id ?? response.headers.get("x-request-id"),
|
|
279
279
|
parsed?.error?.details ?? {}
|
|
@@ -291,7 +291,7 @@ var ApiClient = class {
|
|
|
291
291
|
}
|
|
292
292
|
throw error;
|
|
293
293
|
}
|
|
294
|
-
async fetchWithTimeout(method,
|
|
294
|
+
async fetchWithTimeout(method, path14, body) {
|
|
295
295
|
const controller = new AbortController();
|
|
296
296
|
const timer = setTimeout(() => controller.abort(), this.timeoutMs);
|
|
297
297
|
const headers = { accept: "application/json" };
|
|
@@ -300,7 +300,7 @@ var ApiClient = class {
|
|
|
300
300
|
if (this.workspaceId) headers["x-veefore-workspace"] = this.workspaceId;
|
|
301
301
|
if (this.environmentId) headers["x-veefore-environment"] = this.environmentId;
|
|
302
302
|
try {
|
|
303
|
-
return await fetch(`${this.controlPlaneUrl}${
|
|
303
|
+
return await fetch(`${this.controlPlaneUrl}${path14}`, {
|
|
304
304
|
method,
|
|
305
305
|
headers,
|
|
306
306
|
body: body === void 0 ? void 0 : JSON.stringify(body),
|
|
@@ -1030,6 +1030,97 @@ async function select(question, real, settings = {}) {
|
|
|
1030
1030
|
draw();
|
|
1031
1031
|
});
|
|
1032
1032
|
}
|
|
1033
|
+
async function selectMany(question, options, settings = {}) {
|
|
1034
|
+
if (options.length === 0) return [];
|
|
1035
|
+
const ticked = options.map((option) => settings.selected?.(option) ?? true);
|
|
1036
|
+
if (!isInteractive()) return options.filter((_, index) => ticked[index]).map((o) => o.value);
|
|
1037
|
+
flushCapture();
|
|
1038
|
+
let cursor = 0;
|
|
1039
|
+
let rendered = 0;
|
|
1040
|
+
const stdin = process.stdin;
|
|
1041
|
+
const stdout = process.stdout;
|
|
1042
|
+
const labelWidth = Math.max(...options.map((option) => [...option.label].length));
|
|
1043
|
+
const draw = () => {
|
|
1044
|
+
const width = columns();
|
|
1045
|
+
const count = ticked.filter(Boolean).length;
|
|
1046
|
+
const lines = [];
|
|
1047
|
+
lines.push(
|
|
1048
|
+
`${brand("?")} ${style.bold(question)} ${style.grey(`(${count}/${options.length})`)}`
|
|
1049
|
+
);
|
|
1050
|
+
if (settings.help) lines.push(` ${style.grey(settings.help)}`);
|
|
1051
|
+
const room = Math.max(3, (process.stdout.rows || 24) - lines.length - 3);
|
|
1052
|
+
const window = Math.min(options.length, room);
|
|
1053
|
+
let first = Math.max(0, Math.min(cursor - (window >> 1), options.length - window));
|
|
1054
|
+
if (window === options.length) first = 0;
|
|
1055
|
+
if (first > 0) lines.push(style.grey(` \u2191 ${first} more`));
|
|
1056
|
+
for (let index = first; index < first + window; index += 1) {
|
|
1057
|
+
const option = options[index];
|
|
1058
|
+
const active = index === cursor;
|
|
1059
|
+
const pointer = active ? brand("\u276F") : " ";
|
|
1060
|
+
const box = ticked[index] ? style.green("\u25C9") : style.grey("\u25EF");
|
|
1061
|
+
const text = option.hint ? option.label.padEnd(labelWidth) : option.label;
|
|
1062
|
+
const hint = option.hint ? ` ${style.grey(option.hint)}` : "";
|
|
1063
|
+
const label = active ? brand(text) : ticked[index] ? text : style.grey(text);
|
|
1064
|
+
lines.push(fit(`${pointer} ${box} ${label}${hint}`, width - 1));
|
|
1065
|
+
}
|
|
1066
|
+
const rest = options.length - (first + window);
|
|
1067
|
+
if (rest > 0) lines.push(style.grey(` \u2193 ${rest} more`));
|
|
1068
|
+
lines.push(style.grey(" \u2191\u2193 move \xB7 space toggle \xB7 a all/none \xB7 enter confirm \xB7 esc cancel"));
|
|
1069
|
+
if (rendered > 0) stdout.write(`\x1B[${rendered}A\r\x1B[J`);
|
|
1070
|
+
stdout.write(`${lines.join("\n")}
|
|
1071
|
+
`);
|
|
1072
|
+
rendered = lines.length;
|
|
1073
|
+
};
|
|
1074
|
+
return new Promise((resolve, reject) => {
|
|
1075
|
+
const wasRaw = stdin.isRaw;
|
|
1076
|
+
const restoreCursor = () => {
|
|
1077
|
+
if (!inApp()) stdout.write("\x1B[?25h");
|
|
1078
|
+
};
|
|
1079
|
+
const onResize = () => draw();
|
|
1080
|
+
const finish = (confirmed) => {
|
|
1081
|
+
stdin.off("keypress", onKey);
|
|
1082
|
+
stdin.setRawMode(wasRaw);
|
|
1083
|
+
stdin.pause();
|
|
1084
|
+
process.stdout.off("resize", onResize);
|
|
1085
|
+
process.off("exit", restoreCursor);
|
|
1086
|
+
if (rendered > 0) stdout.write(`\x1B[${rendered}A\r\x1B[J`);
|
|
1087
|
+
restoreCursor();
|
|
1088
|
+
if (!confirmed) {
|
|
1089
|
+
stdout.write(`${style.grey("\u2717")} ${question} ${style.grey("cancelled")}
|
|
1090
|
+
`);
|
|
1091
|
+
reject(new MenuCancelled());
|
|
1092
|
+
return;
|
|
1093
|
+
}
|
|
1094
|
+
const chosen = options.filter((_, index) => ticked[index]);
|
|
1095
|
+
const summary = chosen.length === options.length ? `all ${options.length}` : chosen.length === 0 ? "none" : `${chosen.length} of ${options.length}`;
|
|
1096
|
+
stdout.write(`${style.green("\u2713")} ${question} ${brand(summary)}
|
|
1097
|
+
`);
|
|
1098
|
+
resolve(chosen.map((option) => option.value));
|
|
1099
|
+
};
|
|
1100
|
+
const onKey = (input, key = {}) => {
|
|
1101
|
+
if (key.ctrl && key.name === "c" || key.name === "escape") return finish(false);
|
|
1102
|
+
if (key.name === "return" || key.name === "enter") return finish(true);
|
|
1103
|
+
if (key.name === "space") ticked[cursor] = !ticked[cursor];
|
|
1104
|
+
else if (input === "a" || input === "A") {
|
|
1105
|
+
const all = ticked.every(Boolean);
|
|
1106
|
+
ticked.fill(!all);
|
|
1107
|
+
} else if (key.name === "up" || key.name === "k") {
|
|
1108
|
+
cursor = (cursor - 1 + options.length) % options.length;
|
|
1109
|
+
} else if (key.name === "down" || key.name === "j" || key.name === "tab") {
|
|
1110
|
+
cursor = (cursor + 1) % options.length;
|
|
1111
|
+
} else return;
|
|
1112
|
+
draw();
|
|
1113
|
+
};
|
|
1114
|
+
process.once("exit", restoreCursor);
|
|
1115
|
+
emitKeypressEvents(stdin);
|
|
1116
|
+
stdin.setRawMode(true);
|
|
1117
|
+
stdin.resume();
|
|
1118
|
+
stdout.write("\x1B[?25l");
|
|
1119
|
+
stdin.on("keypress", onKey);
|
|
1120
|
+
process.stdout.on("resize", onResize);
|
|
1121
|
+
draw();
|
|
1122
|
+
});
|
|
1123
|
+
}
|
|
1033
1124
|
async function selectYesNo(question, defaultYes = false) {
|
|
1034
1125
|
return select(
|
|
1035
1126
|
question,
|
|
@@ -1946,11 +2037,11 @@ function renderRevokeConfirmation(agent, environment, preview) {
|
|
|
1946
2037
|
out(` ${style.red("This action is irreversible for this identity.")}`);
|
|
1947
2038
|
out();
|
|
1948
2039
|
}
|
|
1949
|
-
async function lifecycle(args, flags,
|
|
2040
|
+
async function lifecycle(args, flags, path14, resultingStatus) {
|
|
1950
2041
|
const context = await resolveContext(flags);
|
|
1951
2042
|
const agentId = positional(args) ?? context.config.agentId;
|
|
1952
2043
|
const agent = await context.api.get(`/v1/agents/${agentId}`);
|
|
1953
|
-
const reasonRequired =
|
|
2044
|
+
const reasonRequired = path14 === "suspend";
|
|
1954
2045
|
const reason = valueFor(args, "--reason");
|
|
1955
2046
|
if (reasonRequired && (!reason || reason.trim().length < 10)) {
|
|
1956
2047
|
throw new CliError(
|
|
@@ -1960,24 +2051,24 @@ async function lifecycle(args, flags, path13, resultingStatus) {
|
|
|
1960
2051
|
}
|
|
1961
2052
|
out();
|
|
1962
2053
|
out(
|
|
1963
|
-
`${symbol.info} ${
|
|
2054
|
+
`${symbol.info} ${path14} ${style.bold(agent.name)} ${style.grey(`(${agent.status} \u2192 ${resultingStatus})`)}`
|
|
1964
2055
|
);
|
|
1965
|
-
if (
|
|
2056
|
+
if (path14 !== "resume") {
|
|
1966
2057
|
const confirmed = await confirm("Continue?", { yes: flags.yes });
|
|
1967
2058
|
if (!confirmed) {
|
|
1968
2059
|
out(style.grey("Cancelled."));
|
|
1969
2060
|
return 1;
|
|
1970
2061
|
}
|
|
1971
2062
|
}
|
|
1972
|
-
const actionSpinner = spinner(`Applying ${
|
|
2063
|
+
const actionSpinner = spinner(`Applying ${path14}`);
|
|
1973
2064
|
try {
|
|
1974
2065
|
await withStepUp(
|
|
1975
2066
|
context.api,
|
|
1976
|
-
() => context.api.post(`/v1/agents/${agentId}/${
|
|
2067
|
+
() => context.api.post(`/v1/agents/${agentId}/${path14}`, reason ? { reason } : {})
|
|
1977
2068
|
);
|
|
1978
2069
|
actionSpinner.succeed(`${agent.name} is ${resultingStatus}`);
|
|
1979
2070
|
} catch (error) {
|
|
1980
|
-
actionSpinner.fail(`Could not ${
|
|
2071
|
+
actionSpinner.fail(`Could not ${path14} the agent`);
|
|
1981
2072
|
throw error;
|
|
1982
2073
|
}
|
|
1983
2074
|
out();
|
|
@@ -2289,6 +2380,7 @@ import { spawnSync } from "node:child_process";
|
|
|
2289
2380
|
import { existsSync as existsSync2, readFileSync as readFileSync2, realpathSync } from "node:fs";
|
|
2290
2381
|
import path3 from "node:path";
|
|
2291
2382
|
var MIN_APPROVAL_WAIT_SDK = "1.1.0";
|
|
2383
|
+
var MIN_CHECKPOINT_SDK = "1.2.0";
|
|
2292
2384
|
function versionLessThan(a, b) {
|
|
2293
2385
|
if (!a) return true;
|
|
2294
2386
|
const left = a.split(/[.+-]/).slice(0, 3).map(Number);
|
|
@@ -2335,7 +2427,8 @@ function installedSdks(root, options = {}) {
|
|
|
2335
2427
|
language: "js",
|
|
2336
2428
|
location: path3.relative(top, path3.dirname(manifest)) || ".",
|
|
2337
2429
|
version,
|
|
2338
|
-
waitsForApproval: !versionLessThan(version, MIN_APPROVAL_WAIT_SDK)
|
|
2430
|
+
waitsForApproval: !versionLessThan(version, MIN_APPROVAL_WAIT_SDK),
|
|
2431
|
+
hasCheckpoint: !versionLessThan(version, MIN_CHECKPOINT_SDK)
|
|
2339
2432
|
});
|
|
2340
2433
|
}
|
|
2341
2434
|
if (options.python) {
|
|
@@ -2352,7 +2445,8 @@ function installedSdks(root, options = {}) {
|
|
|
2352
2445
|
language: "py",
|
|
2353
2446
|
location: "python: veefore-py",
|
|
2354
2447
|
version,
|
|
2355
|
-
waitsForApproval: !versionLessThan(version, MIN_APPROVAL_WAIT_SDK)
|
|
2448
|
+
waitsForApproval: !versionLessThan(version, MIN_APPROVAL_WAIT_SDK),
|
|
2449
|
+
hasCheckpoint: !versionLessThan(version, MIN_CHECKPOINT_SDK)
|
|
2356
2450
|
});
|
|
2357
2451
|
}
|
|
2358
2452
|
}
|
|
@@ -3300,12 +3394,12 @@ function isLoosening(from, to) {
|
|
|
3300
3394
|
}
|
|
3301
3395
|
|
|
3302
3396
|
// src/commands/init.ts
|
|
3303
|
-
import { readFileSync as
|
|
3304
|
-
import
|
|
3397
|
+
import { readFileSync as readFileSync10 } from "node:fs";
|
|
3398
|
+
import path13 from "node:path";
|
|
3305
3399
|
|
|
3306
3400
|
// src/integrate.ts
|
|
3307
|
-
import { readFileSync as
|
|
3308
|
-
import
|
|
3401
|
+
import { readFileSync as readFileSync9 } from "node:fs";
|
|
3402
|
+
import path10 from "node:path";
|
|
3309
3403
|
|
|
3310
3404
|
// ../types/src/permissions.ts
|
|
3311
3405
|
var PERMISSIONS = [
|
|
@@ -4189,10 +4283,33 @@ function printDispatchInstructions(items, where) {
|
|
|
4189
4283
|
out(
|
|
4190
4284
|
` ${items.length} tool${items.length === 1 ? " is" : "s are"} defined as data, so no one function performs each tool.`
|
|
4191
4285
|
);
|
|
4192
|
-
|
|
4193
|
-
|
|
4194
|
-
|
|
4195
|
-
|
|
4286
|
+
const keys = [...new Set(items.map((item) => item.key))];
|
|
4287
|
+
const sites = new Map(
|
|
4288
|
+
keys.flatMap((key) => {
|
|
4289
|
+
const site = where?.sites?.get(key);
|
|
4290
|
+
return site ? [[key, site]] : [];
|
|
4291
|
+
})
|
|
4292
|
+
);
|
|
4293
|
+
const places = new Set([...sites.values()].map((site) => `${site.file}:${site.line}`));
|
|
4294
|
+
const only = places.size === 1 && sites.size === keys.length ? [...sites.values()][0] : null;
|
|
4295
|
+
if (sites.size > 0 && !only) {
|
|
4296
|
+
out(" Wrap the call that performs each one, where your code runs it:");
|
|
4297
|
+
out();
|
|
4298
|
+
for (const key of keys) {
|
|
4299
|
+
const site = sites.get(key);
|
|
4300
|
+
out(
|
|
4301
|
+
` ${style.bold(key)} ${style.grey(site ? `${site.file}:${site.line}` : "where it is run; this could not be found")}`
|
|
4302
|
+
);
|
|
4303
|
+
if (site) out(` ${style.grey(site.code)}`);
|
|
4304
|
+
}
|
|
4305
|
+
out();
|
|
4306
|
+
out(" The change is the same shape in each place:");
|
|
4307
|
+
} else if (only || where) {
|
|
4308
|
+
const file = only?.file ?? where?.file;
|
|
4309
|
+
const line = only?.line ?? where?.line ?? 1;
|
|
4310
|
+
const named = where?.functionName && file === where.file ? ` (${where.functionName})` : "";
|
|
4311
|
+
out(` Your code runs them in ${style.bold(`${file}:${line}`)}${named}.`);
|
|
4312
|
+
if (only) out(` ${style.grey(only.code)}`);
|
|
4196
4313
|
out(" Wrap the call there that performs the chosen tool, once:");
|
|
4197
4314
|
} else {
|
|
4198
4315
|
out(" Wrap the code that runs whichever tool the model picked, once:");
|
|
@@ -4210,7 +4327,9 @@ function printDispatchInstructions(items, where) {
|
|
|
4210
4327
|
out(` ${style.cyan("import { guard } from '@veefore/sdk';")}`);
|
|
4211
4328
|
out();
|
|
4212
4329
|
out(` ${style.grey("// before: const result = await runTool(call);")}`);
|
|
4213
|
-
out(
|
|
4330
|
+
out(
|
|
4331
|
+
` ${style.cyan("const result = await guard(call.name, (_args) => runTool(call))(call.args);")}`
|
|
4332
|
+
);
|
|
4214
4333
|
}
|
|
4215
4334
|
out();
|
|
4216
4335
|
out(
|
|
@@ -4230,6 +4349,9 @@ function findDispatchGuards(sources) {
|
|
|
4230
4349
|
const dynamic = /(?<![\w$.])[A-Za-z_$]*[gG]uard\(\s*(?!['"`])[A-Za-z_$][\w$.]*(?:\[[^\]\n]{1,40}\])?\s*[,)]/;
|
|
4231
4350
|
return sources.filter(({ text }) => imports.test(text) && dynamic.test(stripDefinitions(text))).map(({ file }) => file);
|
|
4232
4351
|
}
|
|
4352
|
+
function escapeRegex(value) {
|
|
4353
|
+
return value.replace(/[.*+?^${}()|[\]\\/]/g, "\\$&");
|
|
4354
|
+
}
|
|
4233
4355
|
function stripDefinitions(text) {
|
|
4234
4356
|
return text.replace(/\b(?:function|def)\s+[A-Za-z_$]*[gG]uard\s*\(/g, "");
|
|
4235
4357
|
}
|
|
@@ -4238,7 +4360,12 @@ function toCamel(key) {
|
|
|
4238
4360
|
}
|
|
4239
4361
|
function findGuardedKeys(sources) {
|
|
4240
4362
|
const found = /* @__PURE__ */ new Map();
|
|
4241
|
-
const pattern = /(?:\b|@)[A-Za-z_$]*[gG]uard\(\s*['"]([a-z][a-z0-9_]*)['"]/g;
|
|
4363
|
+
const pattern = /(?:\b|@)(?:[A-Za-z_$]*[gG]uard|[A-Za-z_$]*[cC]heckpoint)\(\s*['"]([a-z][a-z0-9_]*)['"]/g;
|
|
4364
|
+
for (const { file, text } of sources) {
|
|
4365
|
+
for (const [key, where] of dynamicCheckpointKeys(text, file)) {
|
|
4366
|
+
if (!found.has(key)) found.set(key, where);
|
|
4367
|
+
}
|
|
4368
|
+
}
|
|
4242
4369
|
for (const { file, text } of sources) {
|
|
4243
4370
|
for (const match of text.matchAll(pattern)) {
|
|
4244
4371
|
const key = match[1];
|
|
@@ -4247,6 +4374,24 @@ function findGuardedKeys(sources) {
|
|
|
4247
4374
|
}
|
|
4248
4375
|
return found;
|
|
4249
4376
|
}
|
|
4377
|
+
function dynamicCheckpointKeys(text, file) {
|
|
4378
|
+
const found = [];
|
|
4379
|
+
const calls = /(?:\b)[A-Za-z_$]*[cC]heckpoint\(\s*([A-Za-z_$][\w$]*(?:\s*(?:\?\.|\.)\s*[A-Za-z_$][\w$]*)+)\s*[,)]/g;
|
|
4380
|
+
for (const call of text.matchAll(calls)) {
|
|
4381
|
+
const expression = (call[1] ?? "").replace(/\s+/g, "");
|
|
4382
|
+
if (expression === "") continue;
|
|
4383
|
+
const head = text.slice(Math.max(0, (call.index ?? 0) - 600), call.index ?? 0);
|
|
4384
|
+
const [object, ...rest] = expression.split(".");
|
|
4385
|
+
if (object === void 0 || rest.length === 0) continue;
|
|
4386
|
+
const access = rest.map((part) => `\\s*(?:\\?\\.|\\.)\\s*${escapeRegex(part)}`).join("");
|
|
4387
|
+
const pattern = new RegExp(
|
|
4388
|
+
`(?<![\\w$.])${escapeRegex(object)}${access}\\s*={2,3}\\s*['"\`]([a-z][a-z0-9_]*)['"\`]`,
|
|
4389
|
+
"g"
|
|
4390
|
+
);
|
|
4391
|
+
for (const match of head.matchAll(pattern)) found.push([match[1], file]);
|
|
4392
|
+
}
|
|
4393
|
+
return found;
|
|
4394
|
+
}
|
|
4250
4395
|
var PROVIDER_ENV = {
|
|
4251
4396
|
anthropic: "ANTHROPIC_API_KEY",
|
|
4252
4397
|
openai: "OPENAI_API_KEY"
|
|
@@ -4651,12 +4796,95 @@ function restoreBackup(root, backup, options = {}) {
|
|
|
4651
4796
|
}
|
|
4652
4797
|
|
|
4653
4798
|
// src/rewrite.ts
|
|
4654
|
-
import { readFileSync as
|
|
4655
|
-
import
|
|
4799
|
+
import { readFileSync as readFileSync8 } from "node:fs";
|
|
4800
|
+
import path9 from "node:path";
|
|
4656
4801
|
|
|
4657
4802
|
// src/scan.ts
|
|
4658
|
-
import { existsSync as existsSync6, readFileSync as
|
|
4659
|
-
import
|
|
4803
|
+
import { existsSync as existsSync6, readFileSync as readFileSync7 } from "node:fs";
|
|
4804
|
+
import path8 from "node:path";
|
|
4805
|
+
|
|
4806
|
+
// src/anchors.ts
|
|
4807
|
+
var TOOL_ANCHORS = [
|
|
4808
|
+
// Schema-defined tools: findToolSchemas requires one of these keys.
|
|
4809
|
+
"parameters",
|
|
4810
|
+
"input_schema",
|
|
4811
|
+
"inputSchema",
|
|
4812
|
+
"parametersJsonSchema",
|
|
4813
|
+
// The OpenAI function shape, which may carry no description at all.
|
|
4814
|
+
"'function'",
|
|
4815
|
+
'"function"',
|
|
4816
|
+
"`function`",
|
|
4817
|
+
// Framework calls and decorators.
|
|
4818
|
+
"tool(",
|
|
4819
|
+
"Tool(",
|
|
4820
|
+
"tool (",
|
|
4821
|
+
"Tool (",
|
|
4822
|
+
"registerTool",
|
|
4823
|
+
// Python decorators, including the `@mcp.tool` / `@agent.tool` forms, which
|
|
4824
|
+
// carry neither `@tool` nor `tool(`.
|
|
4825
|
+
"@tool",
|
|
4826
|
+
".tool",
|
|
4827
|
+
"function_tool",
|
|
4828
|
+
"StructuredTool",
|
|
4829
|
+
"DynamicTool"
|
|
4830
|
+
];
|
|
4831
|
+
var AGENT_ANCHORS = [
|
|
4832
|
+
// Model SDKs and agent frameworks.
|
|
4833
|
+
"openai",
|
|
4834
|
+
"OpenAI",
|
|
4835
|
+
"@anthropic-ai/sdk",
|
|
4836
|
+
"anthropic",
|
|
4837
|
+
"Anthropic",
|
|
4838
|
+
"@google/genai",
|
|
4839
|
+
"@google/generative-ai",
|
|
4840
|
+
"google.generativeai",
|
|
4841
|
+
"google-generativeai",
|
|
4842
|
+
"langchain",
|
|
4843
|
+
"langgraph",
|
|
4844
|
+
"crewai",
|
|
4845
|
+
"llama_index",
|
|
4846
|
+
"llamaindex",
|
|
4847
|
+
"autogen",
|
|
4848
|
+
"litellm",
|
|
4849
|
+
"mistralai",
|
|
4850
|
+
"ollama",
|
|
4851
|
+
"bedrock-runtime",
|
|
4852
|
+
"bedrock_runtime",
|
|
4853
|
+
"@modelcontextprotocol",
|
|
4854
|
+
"mcp.server",
|
|
4855
|
+
// The vocabulary of a tool call, whatever produced it.
|
|
4856
|
+
"tool_calls",
|
|
4857
|
+
"toolCalls",
|
|
4858
|
+
"tool_call",
|
|
4859
|
+
"toolCall",
|
|
4860
|
+
"function_call",
|
|
4861
|
+
"functionCall",
|
|
4862
|
+
"tool_choice",
|
|
4863
|
+
"toolChoice",
|
|
4864
|
+
"toolUse",
|
|
4865
|
+
"tool_use"
|
|
4866
|
+
];
|
|
4867
|
+
var GUARD_ANCHORS = ["uard(", "uard (", "checkpoint("];
|
|
4868
|
+
var RISK_ANCHORS = [
|
|
4869
|
+
"://",
|
|
4870
|
+
"process.env",
|
|
4871
|
+
"import.meta.env",
|
|
4872
|
+
"os.environ",
|
|
4873
|
+
"getenv",
|
|
4874
|
+
"Deno.env"
|
|
4875
|
+
];
|
|
4876
|
+
function toolKeyAnchors(toolKeys) {
|
|
4877
|
+
const anchors = /* @__PURE__ */ new Set();
|
|
4878
|
+
for (const key of toolKeys) {
|
|
4879
|
+
anchors.add(key);
|
|
4880
|
+
const camel = key.replace(/_([a-z0-9])/g, (_match, char) => char.toUpperCase());
|
|
4881
|
+
if (camel !== key) anchors.add(camel);
|
|
4882
|
+
}
|
|
4883
|
+
return [...anchors];
|
|
4884
|
+
}
|
|
4885
|
+
var SCAN_ANCHORS = [
|
|
4886
|
+
.../* @__PURE__ */ new Set([...TOOL_ANCHORS, ...AGENT_ANCHORS, ...GUARD_ANCHORS, ...RISK_ANCHORS])
|
|
4887
|
+
];
|
|
4660
4888
|
|
|
4661
4889
|
// src/framework.ts
|
|
4662
4890
|
import { existsSync as existsSync5, readFileSync as readFileSync5, readdirSync as readdirSync2 } from "node:fs";
|
|
@@ -4852,19 +5080,11 @@ function integrationSnippet(framework) {
|
|
|
4852
5080
|
].join("\n");
|
|
4853
5081
|
}
|
|
4854
5082
|
|
|
4855
|
-
// src/
|
|
4856
|
-
|
|
4857
|
-
|
|
4858
|
-
|
|
4859
|
-
|
|
4860
|
-
const currency = CURRENCY_PARAMETER.exec(definition)?.[1] ?? null;
|
|
4861
|
-
if (!amount && !currency) return null;
|
|
4862
|
-
return {
|
|
4863
|
-
amountParameter: amount,
|
|
4864
|
-
amountUnit: amount ? "MINOR" : null,
|
|
4865
|
-
currencyParameter: currency
|
|
4866
|
-
};
|
|
4867
|
-
}
|
|
5083
|
+
// src/search.ts
|
|
5084
|
+
import { execFileSync } from "node:child_process";
|
|
5085
|
+
import { mkdtempSync, readFileSync as readFileSync6, readdirSync as readdirSync3, rmSync as rmSync3, statSync as statSync4, writeFileSync as writeFileSync4 } from "node:fs";
|
|
5086
|
+
import { tmpdir } from "node:os";
|
|
5087
|
+
import path7 from "node:path";
|
|
4868
5088
|
var SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
4869
5089
|
".ts",
|
|
4870
5090
|
".tsx",
|
|
@@ -4900,8 +5120,260 @@ var SKIP_DIRECTORIES = /* @__PURE__ */ new Set([
|
|
|
4900
5120
|
"tests",
|
|
4901
5121
|
"test"
|
|
4902
5122
|
]);
|
|
4903
|
-
var
|
|
4904
|
-
var
|
|
5123
|
+
var SKIP_FILES = /\.(test|spec)\.[cm]?[jt]sx?$|^test_.*\.py$|_test\.py$|\.d\.ts$|\.min\.[cm]?js$/;
|
|
5124
|
+
var DEFAULT_BUDGET_MS = 6e4;
|
|
5125
|
+
var MAX_PARSE_BYTES = 4 * 1024 * 1024;
|
|
5126
|
+
var cache = /* @__PURE__ */ new Map();
|
|
5127
|
+
function forgetIndex(root) {
|
|
5128
|
+
if (root === void 0) cache.clear();
|
|
5129
|
+
else cache.delete(path7.resolve(root));
|
|
5130
|
+
}
|
|
5131
|
+
function indexFiles(root, options = {}) {
|
|
5132
|
+
const key = path7.resolve(root);
|
|
5133
|
+
const hit = cache.get(key);
|
|
5134
|
+
if (hit) return hit;
|
|
5135
|
+
const built = buildIndex(root, options);
|
|
5136
|
+
if (!built.truncated) cache.set(key, built);
|
|
5137
|
+
return built;
|
|
5138
|
+
}
|
|
5139
|
+
function buildIndex(root, options) {
|
|
5140
|
+
const deadline2 = Date.now() + (options.budgetMs ?? DEFAULT_BUDGET_MS);
|
|
5141
|
+
const fromGit = gitFiles(root, deadline2);
|
|
5142
|
+
if (fromGit) {
|
|
5143
|
+
return {
|
|
5144
|
+
files: fromGit.tracked.filter(isCandidate),
|
|
5145
|
+
method: "git",
|
|
5146
|
+
truncated: false,
|
|
5147
|
+
unsearchable: fromGit.unsearchable.filter(isCandidate)
|
|
5148
|
+
};
|
|
5149
|
+
}
|
|
5150
|
+
const files = [];
|
|
5151
|
+
const truncated = walk(root, root, files, deadline2);
|
|
5152
|
+
return { files, method: "walk", truncated, unsearchable: [] };
|
|
5153
|
+
}
|
|
5154
|
+
function gitFiles(root, deadline2) {
|
|
5155
|
+
const output = git(
|
|
5156
|
+
root,
|
|
5157
|
+
["ls-files", "-z", "--cached", "--others", "--exclude-standard"],
|
|
5158
|
+
deadline2
|
|
5159
|
+
);
|
|
5160
|
+
if (output === null) return null;
|
|
5161
|
+
const tracked = new Set(output.split("\0").filter((entry) => entry !== ""));
|
|
5162
|
+
const unsearchable = /* @__PURE__ */ new Set();
|
|
5163
|
+
const submodules = git(root, ["ls-files", "-z", "--cached", "--recurse-submodules"], deadline2);
|
|
5164
|
+
if (submodules !== null) {
|
|
5165
|
+
for (const entry of submodules.split("\0")) {
|
|
5166
|
+
if (entry !== "" && !tracked.has(entry)) unsearchable.add(entry);
|
|
5167
|
+
}
|
|
5168
|
+
}
|
|
5169
|
+
for (const file of ignoredSources(root, deadline2)) {
|
|
5170
|
+
if (!tracked.has(file)) unsearchable.add(file);
|
|
5171
|
+
}
|
|
5172
|
+
return { tracked: [...tracked, ...unsearchable], unsearchable: [...unsearchable] };
|
|
5173
|
+
}
|
|
5174
|
+
function ignoredSources(root, deadline2) {
|
|
5175
|
+
const output = git(
|
|
5176
|
+
root,
|
|
5177
|
+
["ls-files", "-z", "--others", "--ignored", "--exclude-standard", "--directory"],
|
|
5178
|
+
deadline2
|
|
5179
|
+
);
|
|
5180
|
+
if (output === null) return [];
|
|
5181
|
+
const files = [];
|
|
5182
|
+
for (const entry of output.split("\0")) {
|
|
5183
|
+
if (entry === "") continue;
|
|
5184
|
+
if (entry.endsWith("/")) {
|
|
5185
|
+
const name = entry.replace(/\/$/, "").split("/").pop() ?? "";
|
|
5186
|
+
if (SKIP_DIRECTORIES.has(name) || name.startsWith(".")) continue;
|
|
5187
|
+
const nested = [];
|
|
5188
|
+
walk(root, path7.join(root, entry), nested, deadline2);
|
|
5189
|
+
files.push(...nested);
|
|
5190
|
+
continue;
|
|
5191
|
+
}
|
|
5192
|
+
if (isCandidate(entry)) files.push(entry);
|
|
5193
|
+
}
|
|
5194
|
+
return files;
|
|
5195
|
+
}
|
|
5196
|
+
function git(root, args, deadline2) {
|
|
5197
|
+
const timeout = deadline2 - Date.now();
|
|
5198
|
+
if (timeout <= 0) return null;
|
|
5199
|
+
try {
|
|
5200
|
+
return execFileSync("git", ["-c", "core.quotepath=off", ...args], {
|
|
5201
|
+
cwd: root,
|
|
5202
|
+
encoding: "utf8",
|
|
5203
|
+
timeout,
|
|
5204
|
+
maxBuffer: 512 * 1024 * 1024,
|
|
5205
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
5206
|
+
});
|
|
5207
|
+
} catch {
|
|
5208
|
+
return null;
|
|
5209
|
+
}
|
|
5210
|
+
}
|
|
5211
|
+
function isCandidate(file) {
|
|
5212
|
+
if (!SOURCE_EXTENSIONS.has(path7.extname(file))) return false;
|
|
5213
|
+
const segments = file.split(/[/\\]/);
|
|
5214
|
+
const name = segments[segments.length - 1] ?? "";
|
|
5215
|
+
if (SKIP_FILES.test(name)) return false;
|
|
5216
|
+
return !segments.slice(0, -1).some((segment) => SKIP_DIRECTORIES.has(segment) || segment.startsWith("."));
|
|
5217
|
+
}
|
|
5218
|
+
function walk(root, dir, out2, deadline2) {
|
|
5219
|
+
let entries;
|
|
5220
|
+
try {
|
|
5221
|
+
entries = readdirSyncSafe(dir);
|
|
5222
|
+
} catch {
|
|
5223
|
+
return false;
|
|
5224
|
+
}
|
|
5225
|
+
for (const entry of entries) {
|
|
5226
|
+
if (Date.now() > deadline2) return true;
|
|
5227
|
+
if (entry.startsWith(".")) continue;
|
|
5228
|
+
const full = path7.join(dir, entry);
|
|
5229
|
+
let directory;
|
|
5230
|
+
try {
|
|
5231
|
+
directory = statSync4(full).isDirectory();
|
|
5232
|
+
} catch {
|
|
5233
|
+
continue;
|
|
5234
|
+
}
|
|
5235
|
+
if (directory) {
|
|
5236
|
+
if (SKIP_DIRECTORIES.has(entry)) continue;
|
|
5237
|
+
if (walk(root, full, out2, deadline2)) return true;
|
|
5238
|
+
continue;
|
|
5239
|
+
}
|
|
5240
|
+
const relative = path7.relative(root, full);
|
|
5241
|
+
if (isCandidate(relative)) out2.push(relative);
|
|
5242
|
+
}
|
|
5243
|
+
return false;
|
|
5244
|
+
}
|
|
5245
|
+
function readdirSyncSafe(dir) {
|
|
5246
|
+
return readdirSync3(dir).sort();
|
|
5247
|
+
}
|
|
5248
|
+
function shortlist(root, index, anchors, options = {}) {
|
|
5249
|
+
const considered = index.files.length;
|
|
5250
|
+
if (anchors.length === 0 || considered === 0) {
|
|
5251
|
+
return { files: [], method: "in-process", considered, truncated: false };
|
|
5252
|
+
}
|
|
5253
|
+
const deadline2 = Date.now() + (options.budgetMs ?? DEFAULT_BUDGET_MS);
|
|
5254
|
+
const allowed = new Set(index.files);
|
|
5255
|
+
const reached = index.unsearchable.filter((file) => allowed.has(file));
|
|
5256
|
+
const extra = reached.length > 0 ? inProcessSearch(root, reached, anchors, deadline2, reached.length) : null;
|
|
5257
|
+
const combine = (files, method) => ({
|
|
5258
|
+
files: [.../* @__PURE__ */ new Set([...files, ...extra?.files ?? []])],
|
|
5259
|
+
method,
|
|
5260
|
+
considered,
|
|
5261
|
+
truncated: extra?.truncated ?? false
|
|
5262
|
+
});
|
|
5263
|
+
if (index.method === "git") {
|
|
5264
|
+
const matched = searchWith(
|
|
5265
|
+
"git",
|
|
5266
|
+
root,
|
|
5267
|
+
["-c", "core.quotepath=off", "grep", "--no-color", "-l", "-I", "-z", "-F", "--untracked"],
|
|
5268
|
+
anchors,
|
|
5269
|
+
deadline2
|
|
5270
|
+
);
|
|
5271
|
+
if (matched) {
|
|
5272
|
+
return combine(
|
|
5273
|
+
matched.filter((file) => allowed.has(file)),
|
|
5274
|
+
"git-grep"
|
|
5275
|
+
);
|
|
5276
|
+
}
|
|
5277
|
+
}
|
|
5278
|
+
const ripgrep = searchWith(
|
|
5279
|
+
"rg",
|
|
5280
|
+
root,
|
|
5281
|
+
["--files-with-matches", "--fixed-strings", "--null", "--no-messages"],
|
|
5282
|
+
anchors,
|
|
5283
|
+
deadline2
|
|
5284
|
+
);
|
|
5285
|
+
if (ripgrep) {
|
|
5286
|
+
return combine(
|
|
5287
|
+
ripgrep.map(normalise).filter((file) => allowed.has(file)),
|
|
5288
|
+
"ripgrep"
|
|
5289
|
+
);
|
|
5290
|
+
}
|
|
5291
|
+
return inProcessSearch(root, index.files, anchors, deadline2, considered);
|
|
5292
|
+
}
|
|
5293
|
+
function searchWith(command, root, args, anchors, deadline2) {
|
|
5294
|
+
const timeout = deadline2 - Date.now();
|
|
5295
|
+
if (timeout <= 0) return null;
|
|
5296
|
+
let directory = null;
|
|
5297
|
+
try {
|
|
5298
|
+
directory = mkdtempSync(path7.join(tmpdir(), "veefore-search-"));
|
|
5299
|
+
const patterns = path7.join(directory, "anchors.txt");
|
|
5300
|
+
writeFileSync4(patterns, `${anchors.join("\n")}
|
|
5301
|
+
`);
|
|
5302
|
+
const output = execFileSync(command, [...args, "-f", patterns], {
|
|
5303
|
+
cwd: root,
|
|
5304
|
+
encoding: "utf8",
|
|
5305
|
+
timeout,
|
|
5306
|
+
maxBuffer: 512 * 1024 * 1024,
|
|
5307
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
5308
|
+
});
|
|
5309
|
+
return output.split("\0").filter((entry) => entry !== "");
|
|
5310
|
+
} catch (error) {
|
|
5311
|
+
const status = error.status;
|
|
5312
|
+
if (status === 1) return [];
|
|
5313
|
+
return null;
|
|
5314
|
+
} finally {
|
|
5315
|
+
if (directory) rmSync3(directory, { recursive: true, force: true });
|
|
5316
|
+
}
|
|
5317
|
+
}
|
|
5318
|
+
function inProcessSearch(root, files, anchors, deadline2, considered) {
|
|
5319
|
+
const needles = anchors.map((anchor) => Buffer.from(anchor, "utf8"));
|
|
5320
|
+
const matched = [];
|
|
5321
|
+
let truncated = false;
|
|
5322
|
+
for (const [position, file] of files.entries()) {
|
|
5323
|
+
if ((position & 63) === 0 && Date.now() > deadline2) {
|
|
5324
|
+
truncated = true;
|
|
5325
|
+
break;
|
|
5326
|
+
}
|
|
5327
|
+
let buffer;
|
|
5328
|
+
try {
|
|
5329
|
+
buffer = readFileSync6(path7.join(root, file));
|
|
5330
|
+
} catch {
|
|
5331
|
+
continue;
|
|
5332
|
+
}
|
|
5333
|
+
const nul = buffer.indexOf(0);
|
|
5334
|
+
if (nul >= 0 && nul < 8192) continue;
|
|
5335
|
+
if (needles.some((needle) => buffer.includes(needle))) matched.push(file);
|
|
5336
|
+
}
|
|
5337
|
+
return { files: matched, method: "in-process", considered, truncated };
|
|
5338
|
+
}
|
|
5339
|
+
function normalise(file) {
|
|
5340
|
+
return file.startsWith(`.${path7.sep}`) || file.startsWith("./") ? file.slice(2) : file;
|
|
5341
|
+
}
|
|
5342
|
+
function tooBigToParse(text) {
|
|
5343
|
+
if (text.length > MAX_PARSE_BYTES) return true;
|
|
5344
|
+
if (text.length < 128 * 1024) return false;
|
|
5345
|
+
const head = text.slice(0, 128 * 1024);
|
|
5346
|
+
const lines = head.split("\n").length;
|
|
5347
|
+
return head.length / lines > 500;
|
|
5348
|
+
}
|
|
5349
|
+
function readFiles(root, files) {
|
|
5350
|
+
const out2 = [];
|
|
5351
|
+
for (const file of files) {
|
|
5352
|
+
let text;
|
|
5353
|
+
try {
|
|
5354
|
+
text = readFileSync6(path7.join(root, file), "utf8");
|
|
5355
|
+
} catch {
|
|
5356
|
+
continue;
|
|
5357
|
+
}
|
|
5358
|
+
if (tooBigToParse(text)) continue;
|
|
5359
|
+
out2.push({ file, text });
|
|
5360
|
+
}
|
|
5361
|
+
return out2;
|
|
5362
|
+
}
|
|
5363
|
+
|
|
5364
|
+
// src/scan.ts
|
|
5365
|
+
var MINOR_AMOUNT_PARAMETER = /["']?\b(amount_minor|amountMinor|amount_cents|amountCents|amount_in_cents|amountInCents|amount_minor_units|amountMinorUnits)\b["']?\s*[:=(]/;
|
|
5366
|
+
var CURRENCY_PARAMETER = /["']?\b(currency_code|currencyCode|currency)\b["']?\s*[:=(]/;
|
|
5367
|
+
function inferMoneyMapping(definition) {
|
|
5368
|
+
const amount = MINOR_AMOUNT_PARAMETER.exec(definition)?.[1] ?? null;
|
|
5369
|
+
const currency = CURRENCY_PARAMETER.exec(definition)?.[1] ?? null;
|
|
5370
|
+
if (!amount && !currency) return null;
|
|
5371
|
+
return {
|
|
5372
|
+
amountParameter: amount,
|
|
5373
|
+
amountUnit: amount ? "MINOR" : null,
|
|
5374
|
+
currencyParameter: currency
|
|
5375
|
+
};
|
|
5376
|
+
}
|
|
4905
5377
|
var MODEL_HOSTS = [
|
|
4906
5378
|
"api.openai.com",
|
|
4907
5379
|
"api.anthropic.com",
|
|
@@ -4918,24 +5390,74 @@ var MODEL_SECRET = /^(OPENAI|ANTHROPIC|GOOGLE|GEMINI|AZURE_OPENAI|MISTRAL|GROQ|C
|
|
|
4918
5390
|
var SECRET_NAME = /(KEY|TOKEN|SECRET|PASSWORD|PASSWD|PRIVATE|CREDENTIAL|AUTH|DSN)(_|$)|^DATABASE_URL$|_CONNECTION_STRING$/;
|
|
4919
5391
|
var NOT_SECRET = /^(VEEFORE_|NODE_|NEXT_PUBLIC_|VITE_|PUBLIC_|NPM_)/;
|
|
4920
5392
|
function listSourceFiles(root = process.cwd()) {
|
|
4921
|
-
|
|
4922
|
-
|
|
4923
|
-
|
|
5393
|
+
return listSources(root).files;
|
|
5394
|
+
}
|
|
5395
|
+
function listSources(root = process.cwd()) {
|
|
5396
|
+
const index = indexFiles(root);
|
|
5397
|
+
return { files: index.files, truncated: index.truncated, method: index.method };
|
|
5398
|
+
}
|
|
5399
|
+
function discoverAgents(root = process.cwd(), reuse) {
|
|
5400
|
+
const scan = reuse ?? scanProject(root);
|
|
5401
|
+
const index = indexFiles(root);
|
|
5402
|
+
const byDirectory = /* @__PURE__ */ new Map();
|
|
5403
|
+
for (const tool of scan.tools) {
|
|
5404
|
+
const directory = nearestManifest(root, tool.location.file);
|
|
5405
|
+
const existing = byDirectory.get(directory);
|
|
5406
|
+
const agent = existing ?? {
|
|
5407
|
+
directory,
|
|
5408
|
+
name: readProjectName(path8.join(root, directory)),
|
|
5409
|
+
files: [],
|
|
5410
|
+
tools: []
|
|
5411
|
+
};
|
|
5412
|
+
if (!agent.files.includes(tool.location.file)) agent.files.push(tool.location.file);
|
|
5413
|
+
agent.tools.push(tool);
|
|
5414
|
+
byDirectory.set(directory, agent);
|
|
5415
|
+
}
|
|
5416
|
+
const known = new Set(scan.tools.map((tool) => tool.location.file));
|
|
5417
|
+
const agentFiles = shortlist(root, index, [...AGENT_ANCHORS]);
|
|
5418
|
+
const unreadable = [];
|
|
5419
|
+
for (const { file, text } of readFiles(root, agentFiles.files)) {
|
|
5420
|
+
if (known.has(file)) continue;
|
|
5421
|
+
const python = file.endsWith(".py");
|
|
5422
|
+
if ((python ? findPythonTools(text) : findJsTools(text)).length > 0) continue;
|
|
5423
|
+
const hint = /\btools?\s*[:=]|\btool_calls\b|\btoolCalls\b|\bfunction_call\b/.exec(text);
|
|
5424
|
+
if (!hint) continue;
|
|
5425
|
+
unreadable.push({
|
|
5426
|
+
file,
|
|
5427
|
+
line: lineLocator(text)(hint.index),
|
|
5428
|
+
reason: `passes tools to a model, but none could be read from this file`
|
|
5429
|
+
});
|
|
5430
|
+
}
|
|
5431
|
+
return {
|
|
5432
|
+
agents: [...byDirectory.values()].sort((a, b) => a.directory.localeCompare(b.directory)),
|
|
5433
|
+
unreadable,
|
|
5434
|
+
filesScanned: scan.filesScanned,
|
|
5435
|
+
truncated: scan.truncated || agentFiles.truncated
|
|
5436
|
+
};
|
|
5437
|
+
}
|
|
5438
|
+
function nearestManifest(root, file) {
|
|
5439
|
+
const MANIFESTS = ["package.json", "pyproject.toml", "go.mod", "Cargo.toml", "setup.py"];
|
|
5440
|
+
let dir = path8.dirname(file);
|
|
5441
|
+
for (let depth = 0; depth < 32; depth += 1) {
|
|
5442
|
+
const absolute = path8.join(root, dir);
|
|
5443
|
+
if (MANIFESTS.some((manifest) => existsSync6(path8.join(absolute, manifest)))) {
|
|
5444
|
+
return dir === "" ? "." : dir;
|
|
5445
|
+
}
|
|
5446
|
+
const parent = path8.dirname(dir);
|
|
5447
|
+
if (parent === dir || dir === "." || dir === "") break;
|
|
5448
|
+
dir = parent;
|
|
5449
|
+
}
|
|
5450
|
+
return ".";
|
|
4924
5451
|
}
|
|
4925
5452
|
function scanProject(root = process.cwd()) {
|
|
4926
5453
|
const framework = detectFramework(root);
|
|
4927
|
-
const
|
|
4928
|
-
const
|
|
5454
|
+
const index = indexFiles(root);
|
|
5455
|
+
const candidates = shortlist(root, index, [...SCAN_ANCHORS]);
|
|
5456
|
+
const truncated = index.truncated || candidates.truncated;
|
|
4929
5457
|
const tools = [];
|
|
4930
5458
|
const hosts = /* @__PURE__ */ new Map();
|
|
4931
5459
|
const secrets = /* @__PURE__ */ new Map();
|
|
4932
|
-
for (const relative of files) {
|
|
4933
|
-
let text;
|
|
4934
|
-
try {
|
|
4935
|
-
text = readFileSync6(path7.join(root, relative), "utf8");
|
|
4936
|
-
} catch {
|
|
4937
|
-
continue;
|
|
4938
|
-
}
|
|
5460
|
+
for (const { file: relative, text } of readFiles(root, candidates.files)) {
|
|
4939
5461
|
const python = relative.endsWith(".py");
|
|
4940
5462
|
const lineOf = lineLocator(text);
|
|
4941
5463
|
const fileHosts = findHosts(text).map((hit) => ({ ...hit, line: lineOf(hit.index) }));
|
|
@@ -4951,7 +5473,7 @@ function scanProject(root = process.cwd()) {
|
|
|
4951
5473
|
found.forEach((hit, position) => {
|
|
4952
5474
|
const end = found[position + 1]?.index ?? text.length;
|
|
4953
5475
|
const start = hit.index;
|
|
4954
|
-
const inRegion = (
|
|
5476
|
+
const inRegion = (index2) => index2 >= start && index2 < end;
|
|
4955
5477
|
const ownHosts = unique(fileHosts.filter((h) => inRegion(h.index)).map((h) => h.host));
|
|
4956
5478
|
const ownSecrets = unique(
|
|
4957
5479
|
fileSecrets.filter((s) => inRegion(s.index) && classifySecret(s.name) === "service").map((s) => s.name)
|
|
@@ -4979,7 +5501,9 @@ function scanProject(root = process.cwd()) {
|
|
|
4979
5501
|
projectName: readProjectName(root),
|
|
4980
5502
|
framework,
|
|
4981
5503
|
entryPoints: findEntryPoints(root),
|
|
4982
|
-
filesScanned: files.length,
|
|
5504
|
+
filesScanned: index.files.length,
|
|
5505
|
+
filesParsed: candidates.files.length,
|
|
5506
|
+
searchMethod: candidates.method,
|
|
4983
5507
|
truncated,
|
|
4984
5508
|
tools: dedupeTools(tools),
|
|
4985
5509
|
hosts: [...hosts.entries()].map(([host, locations]) => ({ host, locations })).sort((a, b) => a.host.localeCompare(b.host)),
|
|
@@ -4997,7 +5521,7 @@ function combineRisk(inferred, declared) {
|
|
|
4997
5521
|
function findJsTools(text) {
|
|
4998
5522
|
const hits = [];
|
|
4999
5523
|
const importsFrom = (pkg) => new RegExp(
|
|
5000
|
-
`from\\s+['"]${
|
|
5524
|
+
`from\\s+['"]${escapeRegex2(pkg)}(/[^'"]*)?['"]|require\\(\\s*['"]${escapeRegex2(pkg)}`
|
|
5001
5525
|
).test(text);
|
|
5002
5526
|
const vercel = importsFrom("ai");
|
|
5003
5527
|
const langchain = importsFrom("@langchain/core") || importsFrom("langchain");
|
|
@@ -5053,18 +5577,8 @@ function findJsTools(text) {
|
|
|
5053
5577
|
}
|
|
5054
5578
|
}
|
|
5055
5579
|
}
|
|
5056
|
-
for (const
|
|
5057
|
-
|
|
5058
|
-
const body = text.slice(index, index + 400);
|
|
5059
|
-
const name = stringField(body, "name");
|
|
5060
|
-
if (name) {
|
|
5061
|
-
hits.push({
|
|
5062
|
-
name,
|
|
5063
|
-
source: "openai-function",
|
|
5064
|
-
index,
|
|
5065
|
-
description: stringField(body, "description")
|
|
5066
|
-
});
|
|
5067
|
-
}
|
|
5580
|
+
for (const hit of findFunctionToolSchemas(text)) {
|
|
5581
|
+
hits.push({ ...hit, source: "openai-function" });
|
|
5068
5582
|
}
|
|
5069
5583
|
addSchemaTools(hits, text, false);
|
|
5070
5584
|
if (mcp) {
|
|
@@ -5128,13 +5642,78 @@ function findToolSchemas(text, python) {
|
|
|
5128
5642
|
}
|
|
5129
5643
|
return hits;
|
|
5130
5644
|
}
|
|
5131
|
-
function
|
|
5132
|
-
const
|
|
5133
|
-
|
|
5134
|
-
|
|
5135
|
-
|
|
5136
|
-
|
|
5137
|
-
|
|
5645
|
+
function findFunctionToolSchemas(text) {
|
|
5646
|
+
const hits = [];
|
|
5647
|
+
const NAME = /\bname\s*:\s*(['"`])([\w.-]{1,80})\1/;
|
|
5648
|
+
for (const match of text.matchAll(/\btype\s*:\s*(['"`])function\1/g)) {
|
|
5649
|
+
const at = match.index ?? 0;
|
|
5650
|
+
const from = at + match[0].length;
|
|
5651
|
+
const sibling = /^[^{}]{0,400}?(?<![\w$.])function\s*:\s*\{/.exec(text.slice(from));
|
|
5652
|
+
if (sibling) {
|
|
5653
|
+
const open = from + sibling[0].length - 1;
|
|
5654
|
+
const close = matchingBrace(text, open);
|
|
5655
|
+
if (close > open) {
|
|
5656
|
+
const inner = text.slice(open, close + 1);
|
|
5657
|
+
const found = NAME.exec(inner);
|
|
5658
|
+
if (found) {
|
|
5659
|
+
hits.push({
|
|
5660
|
+
name: found[2],
|
|
5661
|
+
index: open + found.index,
|
|
5662
|
+
description: stringField(inner, "description")
|
|
5663
|
+
});
|
|
5664
|
+
continue;
|
|
5665
|
+
}
|
|
5666
|
+
}
|
|
5667
|
+
}
|
|
5668
|
+
const opener = text.lastIndexOf("{", at);
|
|
5669
|
+
const windows = [
|
|
5670
|
+
[
|
|
5671
|
+
Math.max(opener + 1, at - 400),
|
|
5672
|
+
opener < 0 ? "" : text.slice(Math.max(opener + 1, at - 400), at)
|
|
5673
|
+
],
|
|
5674
|
+
[from, text.slice(from, from + 400).split(/[{}]/)[0] ?? ""]
|
|
5675
|
+
];
|
|
5676
|
+
for (const [offset, window] of windows) {
|
|
5677
|
+
const found = NAME.exec(window);
|
|
5678
|
+
if (!found) continue;
|
|
5679
|
+
hits.push({
|
|
5680
|
+
name: found[2],
|
|
5681
|
+
index: offset + found.index,
|
|
5682
|
+
description: stringField(window, "description")
|
|
5683
|
+
});
|
|
5684
|
+
break;
|
|
5685
|
+
}
|
|
5686
|
+
}
|
|
5687
|
+
return hits;
|
|
5688
|
+
}
|
|
5689
|
+
function matchingBrace(text, open) {
|
|
5690
|
+
let depth = 0;
|
|
5691
|
+
for (let index = open; index < text.length; index += 1) {
|
|
5692
|
+
const char = text[index];
|
|
5693
|
+
if (char === '"' || char === "'" || char === "`") {
|
|
5694
|
+
const quote2 = char;
|
|
5695
|
+
index += 1;
|
|
5696
|
+
while (index < text.length && text[index] !== quote2) {
|
|
5697
|
+
if (text[index] === "\\") index += 1;
|
|
5698
|
+
index += 1;
|
|
5699
|
+
}
|
|
5700
|
+
continue;
|
|
5701
|
+
}
|
|
5702
|
+
if (char === "{") depth += 1;
|
|
5703
|
+
else if (char === "}") {
|
|
5704
|
+
depth -= 1;
|
|
5705
|
+
if (depth === 0) return index;
|
|
5706
|
+
}
|
|
5707
|
+
}
|
|
5708
|
+
return -1;
|
|
5709
|
+
}
|
|
5710
|
+
function addSchemaTools(hits, text, python) {
|
|
5711
|
+
const known = new Set(hits.map((hit) => toToolKey(hit.name)));
|
|
5712
|
+
for (const hit of findToolSchemas(text, python)) {
|
|
5713
|
+
const key = toToolKey(hit.name);
|
|
5714
|
+
if (known.has(key)) continue;
|
|
5715
|
+
known.add(key);
|
|
5716
|
+
hits.push({ ...hit, source: "tool-schema" });
|
|
5138
5717
|
}
|
|
5139
5718
|
}
|
|
5140
5719
|
function objectLiterals(text, python) {
|
|
@@ -5419,52 +5998,21 @@ function toToolKey(name) {
|
|
|
5419
5998
|
const safe = /^[a-z]/.test(key) ? key : `tool_${key}`;
|
|
5420
5999
|
return safe.slice(0, 80) || "tool";
|
|
5421
6000
|
}
|
|
5422
|
-
function walk(root, dir, out2) {
|
|
5423
|
-
let entries;
|
|
5424
|
-
try {
|
|
5425
|
-
entries = readdirSync3(dir).sort();
|
|
5426
|
-
} catch {
|
|
5427
|
-
return false;
|
|
5428
|
-
}
|
|
5429
|
-
for (const entry of entries) {
|
|
5430
|
-
if (out2.length >= MAX_FILES) return true;
|
|
5431
|
-
if (entry.startsWith(".") && entry !== ".") {
|
|
5432
|
-
continue;
|
|
5433
|
-
}
|
|
5434
|
-
const full = path7.join(dir, entry);
|
|
5435
|
-
let stats;
|
|
5436
|
-
try {
|
|
5437
|
-
stats = statSync4(full);
|
|
5438
|
-
} catch {
|
|
5439
|
-
continue;
|
|
5440
|
-
}
|
|
5441
|
-
if (stats.isDirectory()) {
|
|
5442
|
-
if (SKIP_DIRECTORIES.has(entry)) continue;
|
|
5443
|
-
if (walk(root, full, out2)) return true;
|
|
5444
|
-
continue;
|
|
5445
|
-
}
|
|
5446
|
-
if (!stats.isFile() || stats.size > MAX_FILE_BYTES) continue;
|
|
5447
|
-
if (!SOURCE_EXTENSIONS.has(path7.extname(entry))) continue;
|
|
5448
|
-
if (/\.(test|spec)\.[cm]?[jt]sx?$|^test_.*\.py$|_test\.py$|\.d\.ts$/.test(entry)) continue;
|
|
5449
|
-
out2.push(path7.relative(root, full));
|
|
5450
|
-
}
|
|
5451
|
-
return false;
|
|
5452
|
-
}
|
|
5453
6001
|
function readProjectName(root) {
|
|
5454
|
-
const pkg =
|
|
6002
|
+
const pkg = path8.join(root, "package.json");
|
|
5455
6003
|
if (existsSync6(pkg)) {
|
|
5456
6004
|
try {
|
|
5457
|
-
const name = JSON.parse(
|
|
6005
|
+
const name = JSON.parse(readFileSync7(pkg, "utf8")).name;
|
|
5458
6006
|
if (typeof name === "string" && name.trim()) return name.replace(/^@[^/]+\//, "").trim();
|
|
5459
6007
|
} catch {
|
|
5460
6008
|
}
|
|
5461
6009
|
}
|
|
5462
|
-
const pyproject =
|
|
6010
|
+
const pyproject = path8.join(root, "pyproject.toml");
|
|
5463
6011
|
if (existsSync6(pyproject)) {
|
|
5464
|
-
const name = /^\s*name\s*=\s*["']([^"']+)["']/m.exec(
|
|
6012
|
+
const name = /^\s*name\s*=\s*["']([^"']+)["']/m.exec(readFileSync7(pyproject, "utf8"))?.[1];
|
|
5465
6013
|
if (name) return name.trim();
|
|
5466
6014
|
}
|
|
5467
|
-
return
|
|
6015
|
+
return path8.basename(root) || null;
|
|
5468
6016
|
}
|
|
5469
6017
|
function stringField(body, field) {
|
|
5470
6018
|
const match = new RegExp(`\\b${field}\\s*:\\s*(['"\`])([^]{1,300}?)\\1`).exec(body);
|
|
@@ -5506,7 +6054,7 @@ function push(map, key, value) {
|
|
|
5506
6054
|
function unique(values) {
|
|
5507
6055
|
return [...new Set(values)];
|
|
5508
6056
|
}
|
|
5509
|
-
function
|
|
6057
|
+
function escapeRegex2(value) {
|
|
5510
6058
|
return value.replace(/[.*+?^${}()|[\]\\/]/g, "\\$&");
|
|
5511
6059
|
}
|
|
5512
6060
|
|
|
@@ -5519,7 +6067,7 @@ function planRewrite(root, files, toolKeys) {
|
|
|
5519
6067
|
for (const file of files) {
|
|
5520
6068
|
let before;
|
|
5521
6069
|
try {
|
|
5522
|
-
before =
|
|
6070
|
+
before = readFileSync8(path9.join(root, file), "utf8");
|
|
5523
6071
|
} catch {
|
|
5524
6072
|
continue;
|
|
5525
6073
|
}
|
|
@@ -5624,27 +6172,20 @@ function rewriteJs(source, wanted) {
|
|
|
5624
6172
|
wrap3(key, match.index ?? 0, start, trimEnd(source, start, handler.end));
|
|
5625
6173
|
}
|
|
5626
6174
|
}
|
|
5627
|
-
|
|
5628
|
-
const name = /\bname\s*:\s*['"]([\w.-]{1,80})['"]/.exec(
|
|
5629
|
-
source.slice(match.index ?? 0, (match.index ?? 0) + 400)
|
|
5630
|
-
)?.[1];
|
|
5631
|
-
if (!name || !wanted.has(toToolKey(name))) continue;
|
|
5632
|
-
changes.push({
|
|
5633
|
-
key: toToolKey(name),
|
|
5634
|
-
line: lineOf(match.index ?? 0),
|
|
5635
|
-
status: "manual",
|
|
5636
|
-
reason: `this is only the schema; wrap the function your code calls for it, e.g. \`guard(${quote(toToolKey(name))}, yourFunction)\``
|
|
5637
|
-
});
|
|
5638
|
-
}
|
|
5639
|
-
changes.push(...schemaOnly(source, false, wanted));
|
|
6175
|
+
changes.push(...schemaOnly(source, false, wanted, new Set(changes.map((c) => c.key))));
|
|
5640
6176
|
const wrapped = edits.length > 0;
|
|
5641
6177
|
let after = applyEdits(source, edits);
|
|
5642
6178
|
if (wrapped) after = addJsImport(after, alias);
|
|
5643
6179
|
return { after, changes: dedupeChanges(changes) };
|
|
5644
6180
|
}
|
|
5645
6181
|
function addJsImport(source, alias) {
|
|
5646
|
-
|
|
5647
|
-
|
|
6182
|
+
return addJsSdkImport(source, "guard", alias);
|
|
6183
|
+
}
|
|
6184
|
+
function addJsSdkImport(source, name, alias) {
|
|
6185
|
+
if (existingImport(source, name, "js")) return source;
|
|
6186
|
+
const specifier = alias === name ? name : `${name} as ${alias}`;
|
|
6187
|
+
const joined = joinExistingSdkImport(source, specifier, alias, name);
|
|
6188
|
+
if (joined) return joined;
|
|
5648
6189
|
const imports = [
|
|
5649
6190
|
...source.matchAll(
|
|
5650
6191
|
/^import\s+(?:type\s+)?(?:[\s\S]*?\sfrom\s*)?['"][^'"\n]+['"]\s*;?[^\S\n]*$/gm
|
|
@@ -5657,11 +6198,33 @@ function addJsImport(source, alias) {
|
|
|
5657
6198
|
import { ${specifier} } from '@veefore/sdk';${source.slice(at)}`;
|
|
5658
6199
|
}
|
|
5659
6200
|
const cjs = /\brequire\(\s*['"][^'"]+['"]\s*\)/.test(source) && !/^\s*export\s/m.test(source);
|
|
5660
|
-
const line = cjs ? `const { ${alias ===
|
|
6201
|
+
const line = cjs ? `const { ${alias === name ? name : `${name}: ${alias}`} } = require('@veefore/sdk');` : `import { ${specifier} } from '@veefore/sdk';`;
|
|
5661
6202
|
const prologue = /^(?:#![^\n]*\n)?(?:\s*(['"])use [a-z ]+\1;?[^\S\n]*\n)*/.exec(source)?.[0] ?? "";
|
|
5662
6203
|
return `${source.slice(0, prologue.length)}${line}
|
|
5663
6204
|
${source.slice(prologue.length)}`;
|
|
5664
6205
|
}
|
|
6206
|
+
function joinExistingSdkImport(source, specifier, alias, name) {
|
|
6207
|
+
const esm = /import\s*\{([^}]*)\}\s*from\s*(['"])@veefore\/sdk\2/.exec(source);
|
|
6208
|
+
if (esm) {
|
|
6209
|
+
const names = (esm[1] ?? "").trim();
|
|
6210
|
+
const replacement = esm[0].replace(
|
|
6211
|
+
`{${esm[1] ?? ""}}`,
|
|
6212
|
+
names === "" ? `{ ${specifier} }` : `{ ${names}, ${specifier} }`
|
|
6213
|
+
);
|
|
6214
|
+
return source.replace(esm[0], replacement);
|
|
6215
|
+
}
|
|
6216
|
+
const cjs = /const\s*\{([^}]*)\}\s*=\s*require\(\s*(['"])@veefore\/sdk\2\s*\)/.exec(source);
|
|
6217
|
+
if (cjs) {
|
|
6218
|
+
const names = (cjs[1] ?? "").trim();
|
|
6219
|
+
const entry = alias === name ? name : `${name}: ${alias}`;
|
|
6220
|
+
const replacement = cjs[0].replace(
|
|
6221
|
+
`{${cjs[1] ?? ""}}`,
|
|
6222
|
+
names === "" ? `{ ${entry} }` : `{ ${names}, ${entry} }`
|
|
6223
|
+
);
|
|
6224
|
+
return source.replace(cjs[0], replacement);
|
|
6225
|
+
}
|
|
6226
|
+
return null;
|
|
6227
|
+
}
|
|
5665
6228
|
function rewritePython(source, wanted) {
|
|
5666
6229
|
const changes = [];
|
|
5667
6230
|
const lines = source.split("\n");
|
|
@@ -5704,7 +6267,7 @@ function rewritePython(source, wanted) {
|
|
|
5704
6267
|
reason: `decorate the function passed as func= with @guard(${JSON.stringify(toToolKey(name))})`
|
|
5705
6268
|
});
|
|
5706
6269
|
}
|
|
5707
|
-
changes.push(...schemaOnly(source, true, wanted));
|
|
6270
|
+
changes.push(...schemaOnly(source, true, wanted, new Set(changes.map((c) => c.key))));
|
|
5708
6271
|
if (inserts.length === 0) return { after: source, changes: dedupeChanges(changes) };
|
|
5709
6272
|
const out2 = [...lines];
|
|
5710
6273
|
for (const insert of [...inserts].sort((a, b) => b.before - a.before)) {
|
|
@@ -5914,9 +6477,63 @@ function topLevelString(text, open, close, name) {
|
|
|
5914
6477
|
const literal = /^(['"`])([\w .-]{1,80})\1$/.exec(text.slice(value.start, value.end));
|
|
5915
6478
|
return literal?.[2] ?? null;
|
|
5916
6479
|
}
|
|
5917
|
-
var DISPATCH_NAME = /^(?:execute|run|call|dispatch|handle|invoke|perform)_?(?:tool|tool_?call|function_?call)s?$/i;
|
|
6480
|
+
var DISPATCH_NAME = /^(?:execute|run|call|dispatch|handle|invoke|perform)[A-Za-z0-9_]*?_?(?:tool|tool_?call|function_?call)s?$/i;
|
|
5918
6481
|
var KEY_PROPERTIES = ["tool", "name", "toolName", "tool_name"];
|
|
5919
6482
|
var ARG_PROPERTIES = ["params", "args", "arguments", "input", "parameters"];
|
|
6483
|
+
function findRunSites(files, read, toolNames) {
|
|
6484
|
+
const sites = /* @__PURE__ */ new Map();
|
|
6485
|
+
if (toolNames.length === 0) return sites;
|
|
6486
|
+
const patterns = (name) => {
|
|
6487
|
+
const quoted = `['"\`]${escapeForRegex(name)}['"\`]`;
|
|
6488
|
+
return [
|
|
6489
|
+
// `switch (call.tool) { case 'x':` — unmistakable.
|
|
6490
|
+
new RegExp(`case\\s+${quoted}`),
|
|
6491
|
+
// `call.name === 'x'`: the key read off the call being run, which is
|
|
6492
|
+
// what a dispatch does and what a view's own state does not.
|
|
6493
|
+
new RegExp(
|
|
6494
|
+
`\\.\\s*[A-Za-z_$][\\w$]*\\s*={2,3}\\s*${quoted}|${quoted}\\s*={2,3}\\s*[A-Za-z_$][\\w$]*\\s*\\.`
|
|
6495
|
+
),
|
|
6496
|
+
// `handlers['x']`, `executors['x']`
|
|
6497
|
+
new RegExp(`\\[\\s*${quoted}\\s*\\]`),
|
|
6498
|
+
// `'x': async (args) => …`, a map of handlers by key.
|
|
6499
|
+
new RegExp(`${quoted}\\s*:\\s*(?:async\\b|function\\b|\\(|[\\w$]+\\s*=>)`),
|
|
6500
|
+
// Any other comparison against the key. `!==` is deliberately not one:
|
|
6501
|
+
// a tool is not run where its name is ruled out, and taking the first
|
|
6502
|
+
// of those sent the instruction to a view that merely checks which
|
|
6503
|
+
// tool is selected.
|
|
6504
|
+
new RegExp(`(?<![!<>=])={2,3}\\s*${quoted}|${quoted}\\s*={2,3}`)
|
|
6505
|
+
];
|
|
6506
|
+
};
|
|
6507
|
+
const best = /* @__PURE__ */ new Map();
|
|
6508
|
+
for (const file of files) {
|
|
6509
|
+
const source = read(file);
|
|
6510
|
+
if (source === null) continue;
|
|
6511
|
+
const lineOf = lineLocator2(source);
|
|
6512
|
+
for (const name of toolNames) {
|
|
6513
|
+
if (!source.includes(name)) continue;
|
|
6514
|
+
for (const [rank, pattern] of patterns(name).entries()) {
|
|
6515
|
+
const found = pattern.exec(source);
|
|
6516
|
+
if (!found) continue;
|
|
6517
|
+
const current = best.get(name);
|
|
6518
|
+
if (current && current.rank <= rank) break;
|
|
6519
|
+
const at = found.index;
|
|
6520
|
+
const start = source.lastIndexOf("\n", at) + 1;
|
|
6521
|
+
const end = source.indexOf("\n", at);
|
|
6522
|
+
best.set(name, {
|
|
6523
|
+
rank,
|
|
6524
|
+
site: {
|
|
6525
|
+
file,
|
|
6526
|
+
line: lineOf(at),
|
|
6527
|
+
code: source.slice(start, end < 0 ? source.length : end).trim().slice(0, 160)
|
|
6528
|
+
}
|
|
6529
|
+
});
|
|
6530
|
+
break;
|
|
6531
|
+
}
|
|
6532
|
+
}
|
|
6533
|
+
}
|
|
6534
|
+
for (const [name, entry] of best) sites.set(name, entry.site);
|
|
6535
|
+
return sites;
|
|
6536
|
+
}
|
|
5920
6537
|
function planDispatch(files, read, toolNames) {
|
|
5921
6538
|
let best = null;
|
|
5922
6539
|
for (const file of files) {
|
|
@@ -5938,27 +6555,36 @@ function planDispatch(files, read, toolNames) {
|
|
|
5938
6555
|
};
|
|
5939
6556
|
}
|
|
5940
6557
|
}
|
|
5941
|
-
|
|
5942
|
-
best = { file, line: lineOf(found.index), score: Number.MAX_SAFE_INTEGER, fn: found.name };
|
|
5943
|
-
}
|
|
6558
|
+
best ??= { file, line: lineOf(found.index), fn: found.name };
|
|
5944
6559
|
}
|
|
5945
|
-
|
|
5946
|
-
|
|
5947
|
-
|
|
5948
|
-
|
|
5949
|
-
)
|
|
5950
|
-
|
|
5951
|
-
const first = toolNames.map((name) => source.search(new RegExp(`case\\s+['"\`]${escapeForRegex(name)}['"\`]`))).filter((index) => index >= 0).sort((a, b) => a - b)[0];
|
|
5952
|
-
best = { file, line: first === void 0 ? 1 : lineOf(first), score: named, fn: null };
|
|
6560
|
+
}
|
|
6561
|
+
const sites = findRunSites(files, read, toolNames);
|
|
6562
|
+
if (!best) {
|
|
6563
|
+
const byFile = /* @__PURE__ */ new Map();
|
|
6564
|
+
for (const site of sites.values()) {
|
|
6565
|
+
byFile.set(site.file, [...byFile.get(site.file) ?? [], site]);
|
|
5953
6566
|
}
|
|
6567
|
+
const chosen = [...byFile.entries()].sort(
|
|
6568
|
+
(a, b) => b[1].length - a[1].length || a[0].localeCompare(b[0])
|
|
6569
|
+
)[0];
|
|
6570
|
+
if (!chosen) return null;
|
|
6571
|
+
const line = Math.min(...chosen[1].map((site) => site.line));
|
|
6572
|
+
return {
|
|
6573
|
+
file: chosen[0],
|
|
6574
|
+
line,
|
|
6575
|
+
functionName: null,
|
|
6576
|
+
after: null,
|
|
6577
|
+
reason: "this file runs the tools by name",
|
|
6578
|
+
sites
|
|
6579
|
+
};
|
|
5954
6580
|
}
|
|
5955
|
-
if (!best) return null;
|
|
5956
6581
|
return {
|
|
5957
6582
|
file: best.file,
|
|
5958
6583
|
line: best.line,
|
|
5959
6584
|
functionName: best.fn,
|
|
5960
6585
|
after: null,
|
|
5961
|
-
reason:
|
|
6586
|
+
reason: `${best.fn}() runs the tools, but where to put the guard inside it is not certain`,
|
|
6587
|
+
sites
|
|
5962
6588
|
};
|
|
5963
6589
|
}
|
|
5964
6590
|
function dispatchFunctions(source, python) {
|
|
@@ -6031,13 +6657,396 @@ function guardDispatch(source, fn, typescript) {
|
|
|
6031
6657
|
);
|
|
6032
6658
|
return { after, at: target.start };
|
|
6033
6659
|
}
|
|
6660
|
+
var CHECKPOINT_ARG_PROPERTIES = ["args", "arguments", "params", "parameters", "input", "payload"];
|
|
6661
|
+
var CONTROL_KEYWORDS = /* @__PURE__ */ new Set([
|
|
6662
|
+
"if",
|
|
6663
|
+
"else",
|
|
6664
|
+
"for",
|
|
6665
|
+
"while",
|
|
6666
|
+
"do",
|
|
6667
|
+
"switch",
|
|
6668
|
+
"catch",
|
|
6669
|
+
"with",
|
|
6670
|
+
"return",
|
|
6671
|
+
"typeof",
|
|
6672
|
+
"await",
|
|
6673
|
+
"new",
|
|
6674
|
+
"throw",
|
|
6675
|
+
"case",
|
|
6676
|
+
"function",
|
|
6677
|
+
"delete",
|
|
6678
|
+
"void",
|
|
6679
|
+
"in",
|
|
6680
|
+
"of",
|
|
6681
|
+
"yield"
|
|
6682
|
+
]);
|
|
6683
|
+
var BRANCH_KEYWORDS = /* @__PURE__ */ new Set(["if", "for", "while"]);
|
|
6684
|
+
function readStructure(source) {
|
|
6685
|
+
const parens = [];
|
|
6686
|
+
const blocks = [];
|
|
6687
|
+
const parenStack = [];
|
|
6688
|
+
const braceStack = [];
|
|
6689
|
+
const closedAt = /* @__PURE__ */ new Map();
|
|
6690
|
+
let awaitingBody = null;
|
|
6691
|
+
let previous = "";
|
|
6692
|
+
let index = 0;
|
|
6693
|
+
while (index < source.length) {
|
|
6694
|
+
const char = source[index];
|
|
6695
|
+
const next = source[index + 1];
|
|
6696
|
+
if (char === "/" && next === "/") {
|
|
6697
|
+
const stop = source.indexOf("\n", index);
|
|
6698
|
+
index = stop < 0 ? source.length : stop;
|
|
6699
|
+
continue;
|
|
6700
|
+
}
|
|
6701
|
+
if (char === "/" && next === "*") {
|
|
6702
|
+
const stop = source.indexOf("*/", index + 2);
|
|
6703
|
+
index = stop < 0 ? source.length : stop + 2;
|
|
6704
|
+
continue;
|
|
6705
|
+
}
|
|
6706
|
+
if (char === '"' || char === "'") {
|
|
6707
|
+
const stop = skipString(source, index);
|
|
6708
|
+
index = stop < 0 ? nextLine(source, index) : stop;
|
|
6709
|
+
previous = "a";
|
|
6710
|
+
continue;
|
|
6711
|
+
}
|
|
6712
|
+
if (char === "`") {
|
|
6713
|
+
const stop = skipTemplate(source, index);
|
|
6714
|
+
index = stop < 0 ? nextLine(source, index) : stop;
|
|
6715
|
+
previous = "a";
|
|
6716
|
+
continue;
|
|
6717
|
+
}
|
|
6718
|
+
if (char === "/" && regexAllowed(source, index, previous)) {
|
|
6719
|
+
const stop = skipRegex(source, index);
|
|
6720
|
+
index = stop < 0 ? nextLine(source, index) : stop;
|
|
6721
|
+
previous = "a";
|
|
6722
|
+
continue;
|
|
6723
|
+
}
|
|
6724
|
+
if (char === "(") parenStack.push(index);
|
|
6725
|
+
else if (char === ")") {
|
|
6726
|
+
const open = parenStack.pop();
|
|
6727
|
+
if (open !== void 0) {
|
|
6728
|
+
const head = headBefore(source, open);
|
|
6729
|
+
const group = { open, close: index, name: head.name, async: head.async };
|
|
6730
|
+
parens.push(group);
|
|
6731
|
+
closedAt.set(index, group);
|
|
6732
|
+
if (!CONTROL_KEYWORDS.has(group.name) && /^\s*:/.test(source.slice(index + 1, index + 40))) {
|
|
6733
|
+
awaitingBody = { async: group.async, depth: braceStack.length, params: group };
|
|
6734
|
+
}
|
|
6735
|
+
}
|
|
6736
|
+
} else if (char === ";" || char === "=") {
|
|
6737
|
+
if (awaitingBody && braceStack.length === awaitingBody.depth) awaitingBody = null;
|
|
6738
|
+
} else if (char === "{") {
|
|
6739
|
+
const kind = bodyKind(source, index, closedAt, awaitingBody);
|
|
6740
|
+
if (kind.fn) awaitingBody = null;
|
|
6741
|
+
braceStack.push({ open: index, ...kind });
|
|
6742
|
+
} else if (char === "}") {
|
|
6743
|
+
const open = braceStack.pop();
|
|
6744
|
+
if (open !== void 0) blocks.push({ ...open, close: index });
|
|
6745
|
+
}
|
|
6746
|
+
if (!/\s/.test(char)) previous = char;
|
|
6747
|
+
index += 1;
|
|
6748
|
+
}
|
|
6749
|
+
for (const open of braceStack) blocks.push({ ...open, close: source.length });
|
|
6750
|
+
return { parens, blocks };
|
|
6751
|
+
}
|
|
6752
|
+
function nextLine(source, from) {
|
|
6753
|
+
const stop = source.indexOf("\n", from);
|
|
6754
|
+
return stop < 0 ? source.length : stop + 1;
|
|
6755
|
+
}
|
|
6756
|
+
function bodyKind(source, index, closedAt, awaitingBody) {
|
|
6757
|
+
const before = significantBefore(source, index);
|
|
6758
|
+
if (before.text === "=>") {
|
|
6759
|
+
let cursor = before.at - 1;
|
|
6760
|
+
while (cursor >= 0 && /\s/.test(source[cursor])) cursor -= 1;
|
|
6761
|
+
const group = source[cursor] === ")" ? closedAt.get(cursor) : void 0;
|
|
6762
|
+
if (group) return { fn: true, async: group.async, params: group };
|
|
6763
|
+
return { fn: true, async: asyncBefore(source, cursor + 1), params: null };
|
|
6764
|
+
}
|
|
6765
|
+
if (before.text === ")") {
|
|
6766
|
+
const group = closedAt.get(before.at);
|
|
6767
|
+
if (!group) return { fn: false, async: false, params: null };
|
|
6768
|
+
if (CONTROL_KEYWORDS.has(group.name)) return { fn: false, async: false, params: null };
|
|
6769
|
+
return { fn: true, async: group.async, params: group };
|
|
6770
|
+
}
|
|
6771
|
+
if (awaitingBody && /[>\])\w$]/.test(before.text)) {
|
|
6772
|
+
return { fn: true, async: awaitingBody.async, params: awaitingBody.params };
|
|
6773
|
+
}
|
|
6774
|
+
return { fn: false, async: false, params: null };
|
|
6775
|
+
}
|
|
6776
|
+
function significantBefore(source, index) {
|
|
6777
|
+
let cursor = index - 1;
|
|
6778
|
+
for (; ; ) {
|
|
6779
|
+
while (cursor >= 0 && /\s/.test(source[cursor])) cursor -= 1;
|
|
6780
|
+
if (cursor >= 1 && source[cursor] === "/" && source[cursor - 1] === "*") {
|
|
6781
|
+
const open = source.lastIndexOf("/*", cursor - 1);
|
|
6782
|
+
if (open < 0) break;
|
|
6783
|
+
cursor = open - 1;
|
|
6784
|
+
continue;
|
|
6785
|
+
}
|
|
6786
|
+
break;
|
|
6787
|
+
}
|
|
6788
|
+
if (cursor < 0) return { text: "", at: 0 };
|
|
6789
|
+
if (cursor >= 1 && source[cursor] === ">" && source[cursor - 1] === "=") {
|
|
6790
|
+
return { text: "=>", at: cursor - 1 };
|
|
6791
|
+
}
|
|
6792
|
+
return { text: source[cursor], at: cursor };
|
|
6793
|
+
}
|
|
6794
|
+
function headBefore(source, parenOpen) {
|
|
6795
|
+
let cursor = parenOpen - 1;
|
|
6796
|
+
while (cursor >= 0 && /\s/.test(source[cursor])) cursor -= 1;
|
|
6797
|
+
let end = cursor + 1;
|
|
6798
|
+
while (cursor >= 0 && /[\w$]/.test(source[cursor])) cursor -= 1;
|
|
6799
|
+
const name = source.slice(cursor + 1, end);
|
|
6800
|
+
let before = cursor;
|
|
6801
|
+
if (name === "") {
|
|
6802
|
+
return { name: "", async: asyncBefore(source, parenOpen) };
|
|
6803
|
+
}
|
|
6804
|
+
if (name === "async") return { name: "", async: true };
|
|
6805
|
+
while (before >= 0 && /[\s*]/.test(source[before])) before -= 1;
|
|
6806
|
+
end = before + 1;
|
|
6807
|
+
while (before >= 0 && /[\w$]/.test(source[before])) before -= 1;
|
|
6808
|
+
const keyword = source.slice(before + 1, end);
|
|
6809
|
+
if (keyword === "function") return { name, async: asyncBefore(source, before + 1) };
|
|
6810
|
+
return { name, async: asyncBefore(source, cursor + 1) };
|
|
6811
|
+
}
|
|
6812
|
+
function asyncBefore(source, at) {
|
|
6813
|
+
let cursor = at - 1;
|
|
6814
|
+
while (cursor >= 0 && /[\s*]/.test(source[cursor])) cursor -= 1;
|
|
6815
|
+
const end = cursor + 1;
|
|
6816
|
+
while (cursor >= 0 && /[\w$]/.test(source[cursor])) cursor -= 1;
|
|
6817
|
+
return source.slice(cursor + 1, end) === "async";
|
|
6818
|
+
}
|
|
6819
|
+
function innermost(list, at) {
|
|
6820
|
+
return enclosing(list, at)[0] ?? null;
|
|
6821
|
+
}
|
|
6822
|
+
function enclosing(list, at) {
|
|
6823
|
+
return list.filter((item) => at > item.open && at <= item.close).sort((a, b) => b.open - a.open);
|
|
6824
|
+
}
|
|
6825
|
+
function enclosingArguments(source, structure, at) {
|
|
6826
|
+
const body = innermost(
|
|
6827
|
+
structure.blocks.filter((block) => block.fn && block.params !== null),
|
|
6828
|
+
at
|
|
6829
|
+
);
|
|
6830
|
+
if (!body?.params) return null;
|
|
6831
|
+
return parameterExpression(source.slice(body.params.open + 1, body.params.close));
|
|
6832
|
+
}
|
|
6833
|
+
function awaitAllowed(structure, at) {
|
|
6834
|
+
const body = innermost(
|
|
6835
|
+
structure.blocks.filter((block) => block.fn),
|
|
6836
|
+
at
|
|
6837
|
+
);
|
|
6838
|
+
return body ? body.async : null;
|
|
6839
|
+
}
|
|
6840
|
+
function planCheckpoints(files, read, toolKeys) {
|
|
6841
|
+
const plans = [];
|
|
6842
|
+
const outstanding = new Set(toolKeys);
|
|
6843
|
+
if (outstanding.size === 0) return plans;
|
|
6844
|
+
for (const file of files) {
|
|
6845
|
+
if (outstanding.size === 0) break;
|
|
6846
|
+
if (file.endsWith(".py") || !JS_EXTENSIONS.test(file)) continue;
|
|
6847
|
+
const source = read(file);
|
|
6848
|
+
if (source === null) continue;
|
|
6849
|
+
const present = [...outstanding].filter(
|
|
6850
|
+
(key) => source.includes(key) || source.includes(camelOf(key))
|
|
6851
|
+
);
|
|
6852
|
+
if (present.length === 0) continue;
|
|
6853
|
+
const structure = readStructure(source);
|
|
6854
|
+
const lineOf = lineLocator2(source);
|
|
6855
|
+
const alias = chooseAliasFor(source, "checkpoint", "js");
|
|
6856
|
+
const sites = /* @__PURE__ */ new Map();
|
|
6857
|
+
for (const key of present) {
|
|
6858
|
+
const site = checkpointSite(source, key, structure);
|
|
6859
|
+
if (!site) continue;
|
|
6860
|
+
const existing = sites.get(site.at);
|
|
6861
|
+
if (existing) existing.keys.push(key);
|
|
6862
|
+
else sites.set(site.at, { keys: [key], site });
|
|
6863
|
+
}
|
|
6864
|
+
if (sites.size === 0) continue;
|
|
6865
|
+
const edits = [];
|
|
6866
|
+
const placed = [];
|
|
6867
|
+
for (const [at, { keys, site }] of sites) {
|
|
6868
|
+
if (/^\s*(?:await\s+)?[A-Za-z_$]*[cC]heckpoint\(/.test(source.slice(at, at + 200))) continue;
|
|
6869
|
+
const many = keys.length > 1;
|
|
6870
|
+
const keyExpression = many && site.keyExpression ? site.keyExpression : quote(keys[0] ?? "");
|
|
6871
|
+
if (many && !site.keyExpression) continue;
|
|
6872
|
+
const args = site.argsExpression ? `, ${site.argsExpression}` : "";
|
|
6873
|
+
edits.push({
|
|
6874
|
+
start: at,
|
|
6875
|
+
end: at,
|
|
6876
|
+
text: `
|
|
6877
|
+
${blockIndent(source, at)}await ${alias}(${keyExpression}${args});`
|
|
6878
|
+
});
|
|
6879
|
+
for (const key of keys) {
|
|
6880
|
+
placed.push({
|
|
6881
|
+
key,
|
|
6882
|
+
line: lineOf(at),
|
|
6883
|
+
shape: site.shape,
|
|
6884
|
+
...many ? { dynamic: true } : {}
|
|
6885
|
+
});
|
|
6886
|
+
outstanding.delete(key);
|
|
6887
|
+
}
|
|
6888
|
+
}
|
|
6889
|
+
if (edits.length === 0) continue;
|
|
6890
|
+
const after = addJsSdkImport(applyEdits(source, edits), "checkpoint", alias);
|
|
6891
|
+
plans.push({ file, before: source, after, placed });
|
|
6892
|
+
}
|
|
6893
|
+
return plans;
|
|
6894
|
+
}
|
|
6895
|
+
function checkpointSite(source, key, structure) {
|
|
6896
|
+
const quoted = `['"\`]${escapeForRegex(key)}['"\`]`;
|
|
6897
|
+
const usable = (at) => awaitAllowed(structure, at) === true;
|
|
6898
|
+
const bodyAfter = (close) => {
|
|
6899
|
+
const brace = /^\s*\{/.exec(source.slice(close + 1, close + 200));
|
|
6900
|
+
return brace ? close + 1 + brace[0].length : null;
|
|
6901
|
+
};
|
|
6902
|
+
for (const match of source.matchAll(new RegExp(`\\bcase\\s+${quoted}\\s*:`, "g"))) {
|
|
6903
|
+
const colon = (match.index ?? 0) + match[0].length;
|
|
6904
|
+
const braced = /^\s*\{/.exec(source.slice(colon));
|
|
6905
|
+
const at = braced ? colon + braced[0].length : colon;
|
|
6906
|
+
if (!usable(at)) continue;
|
|
6907
|
+
const discriminant = switchKey(source, match.index ?? 0);
|
|
6908
|
+
return {
|
|
6909
|
+
at,
|
|
6910
|
+
shape: "case",
|
|
6911
|
+
argsExpression: discriminant ? argumentsOf(source, discriminant.object) : null,
|
|
6912
|
+
keyExpression: discriminant?.expression ?? null
|
|
6913
|
+
};
|
|
6914
|
+
}
|
|
6915
|
+
for (const match of source.matchAll(new RegExp(quoted, "g"))) {
|
|
6916
|
+
const at = match.index ?? 0;
|
|
6917
|
+
if (!positiveComparison(source, at)) continue;
|
|
6918
|
+
const group = enclosing(structure.parens, at).find(
|
|
6919
|
+
(candidate) => BRANCH_KEYWORDS.has(candidate.name)
|
|
6920
|
+
);
|
|
6921
|
+
if (!group) continue;
|
|
6922
|
+
const insertAt = bodyAfter(group.close);
|
|
6923
|
+
if (insertAt === null || !usable(insertAt)) continue;
|
|
6924
|
+
const compared = comparedExpression(source, at);
|
|
6925
|
+
const loop = group.name === "for" ? loopVariable(source, group.open) : null;
|
|
6926
|
+
const subject = loop ?? compared?.object ?? null;
|
|
6927
|
+
return {
|
|
6928
|
+
at: insertAt,
|
|
6929
|
+
shape: group.name === "if" ? "branch" : "loop",
|
|
6930
|
+
argsExpression: subject ? argumentsOf(source, subject) : enclosingArguments(source, structure, insertAt),
|
|
6931
|
+
keyExpression: loop ? `${loop}.name` : compared?.expression ?? null
|
|
6932
|
+
};
|
|
6933
|
+
}
|
|
6934
|
+
const declaration = new RegExp(
|
|
6935
|
+
`\\b(?:const|let|var)\\s+([A-Za-z_$][\\w$]*)\\s*(?::[^=\\n]{0,120})?=[^;\\n]{0,400}?${quoted}[^;]{0,200}?;`,
|
|
6936
|
+
"g"
|
|
6937
|
+
);
|
|
6938
|
+
for (const match of source.matchAll(declaration)) {
|
|
6939
|
+
const name = match[1];
|
|
6940
|
+
const end = (match.index ?? 0) + match[0].length;
|
|
6941
|
+
const block = innermost(structure.blocks, end);
|
|
6942
|
+
const limit = block ? block.close : source.length;
|
|
6943
|
+
const head = new RegExp(`\\bif\\s*\\(\\s*${escapeForRegex(name)}\\b`).exec(
|
|
6944
|
+
source.slice(end, limit)
|
|
6945
|
+
);
|
|
6946
|
+
if (!head) continue;
|
|
6947
|
+
const parenOpen = end + (head.index ?? 0) + head[0].indexOf("(");
|
|
6948
|
+
const group = structure.parens.find((candidate) => candidate.open === parenOpen);
|
|
6949
|
+
if (!group) continue;
|
|
6950
|
+
const insertAt = bodyAfter(group.close);
|
|
6951
|
+
if (insertAt === null || !usable(insertAt)) continue;
|
|
6952
|
+
return {
|
|
6953
|
+
at: insertAt,
|
|
6954
|
+
shape: "found",
|
|
6955
|
+
argsExpression: argumentsOf(source, name),
|
|
6956
|
+
keyExpression: `${name}.name`
|
|
6957
|
+
};
|
|
6958
|
+
}
|
|
6959
|
+
const handler = new RegExp(
|
|
6960
|
+
`${quoted}\\s*:\\s*(?:async\\s*)?\\(([^)]{0,200})\\)\\s*(?::[^=>{]{0,120})?=>\\s*\\{`,
|
|
6961
|
+
"g"
|
|
6962
|
+
);
|
|
6963
|
+
for (const match of source.matchAll(handler)) {
|
|
6964
|
+
const at = (match.index ?? 0) + match[0].length;
|
|
6965
|
+
if (!usable(at)) continue;
|
|
6966
|
+
return {
|
|
6967
|
+
at,
|
|
6968
|
+
shape: "handler",
|
|
6969
|
+
argsExpression: parameterExpression(match[1] ?? ""),
|
|
6970
|
+
keyExpression: null
|
|
6971
|
+
};
|
|
6972
|
+
}
|
|
6973
|
+
const named = new RegExp(
|
|
6974
|
+
`\\basync\\s+function\\s+(?:${escapeForRegex(camelOf(key))}|${escapeForRegex(key)})\\s*\\(`,
|
|
6975
|
+
"g"
|
|
6976
|
+
);
|
|
6977
|
+
for (const match of source.matchAll(named)) {
|
|
6978
|
+
const parenOpen = (match.index ?? 0) + match[0].length - 1;
|
|
6979
|
+
const group = structure.parens.find((candidate) => candidate.open === parenOpen);
|
|
6980
|
+
if (!group) continue;
|
|
6981
|
+
const body = structure.blocks.filter((block) => block.fn && block.open > group.close).sort((a, b) => a.open - b.open)[0];
|
|
6982
|
+
if (!body) continue;
|
|
6983
|
+
return {
|
|
6984
|
+
at: body.open + 1,
|
|
6985
|
+
shape: "function",
|
|
6986
|
+
argsExpression: parameterExpression(source.slice(group.open + 1, group.close)),
|
|
6987
|
+
keyExpression: null
|
|
6988
|
+
};
|
|
6989
|
+
}
|
|
6990
|
+
return null;
|
|
6991
|
+
}
|
|
6992
|
+
function positiveComparison(source, at) {
|
|
6993
|
+
const before = source.slice(Math.max(0, at - 96), at);
|
|
6994
|
+
return /(?<![!<>])={2,3}\s*$/.test(before);
|
|
6995
|
+
}
|
|
6996
|
+
function comparedExpression(source, at) {
|
|
6997
|
+
const before = source.slice(Math.max(0, at - 160), at);
|
|
6998
|
+
const match = /([A-Za-z_$][\w$]*)\s*(?:\?\.|\.)\s*([A-Za-z_$][\w$]*)\s*={2,3}\s*$/.exec(before);
|
|
6999
|
+
if (!match) return null;
|
|
7000
|
+
return { object: match[1], expression: `${match[1]}.${match[2]}` };
|
|
7001
|
+
}
|
|
7002
|
+
function switchKey(source, at) {
|
|
7003
|
+
const before = source.slice(Math.max(0, at - 4e3), at);
|
|
7004
|
+
const found = [
|
|
7005
|
+
...before.matchAll(/\bswitch\s*\(\s*([A-Za-z_$][\w$]*)\s*(?:\?\.|\.)\s*([A-Za-z_$][\w$]*)/g)
|
|
7006
|
+
];
|
|
7007
|
+
const last = found[found.length - 1];
|
|
7008
|
+
return last ? { object: last[1], expression: `${last[1]}.${last[2]}` } : null;
|
|
7009
|
+
}
|
|
7010
|
+
function loopVariable(source, parenOpen) {
|
|
7011
|
+
return /^\s*\(\s*(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s+of\b/.exec(
|
|
7012
|
+
source.slice(parenOpen, parenOpen + 120)
|
|
7013
|
+
)?.[1] ?? null;
|
|
7014
|
+
}
|
|
7015
|
+
function parameterExpression(parameters) {
|
|
7016
|
+
const names = parameters.split(",").map((part) => /^\s*(?:\.\.\.)?([A-Za-z_$][\w$]*)/.exec(part)?.[1]).filter((name) => name !== void 0);
|
|
7017
|
+
const named = names.find((name) => CHECKPOINT_ARG_PROPERTIES.includes(name));
|
|
7018
|
+
if (named) return named;
|
|
7019
|
+
return names.length === 1 ? names[0] : null;
|
|
7020
|
+
}
|
|
7021
|
+
function camelOf(key) {
|
|
7022
|
+
return key.replace(/_([a-z0-9])/g, (_match, char) => char.toUpperCase());
|
|
7023
|
+
}
|
|
7024
|
+
function argumentsOf(source, object) {
|
|
7025
|
+
for (const property of CHECKPOINT_ARG_PROPERTIES) {
|
|
7026
|
+
const pattern = new RegExp(`\\b${escapeForRegex(object)}\\s*(?:\\?\\.|\\.)\\s*${property}\\b`);
|
|
7027
|
+
if (pattern.test(source)) return `${object}.${property}`;
|
|
7028
|
+
}
|
|
7029
|
+
return null;
|
|
7030
|
+
}
|
|
7031
|
+
function blockIndent(source, at) {
|
|
7032
|
+
const lineEnd = source.indexOf("\n", at);
|
|
7033
|
+
if (lineEnd >= 0) {
|
|
7034
|
+
const next = /^([ \t]*)\S/.exec(source.slice(lineEnd + 1, lineEnd + 200));
|
|
7035
|
+
if (next && !source.slice(lineEnd + 1).trimStart().startsWith("}"))
|
|
7036
|
+
return next[1];
|
|
7037
|
+
}
|
|
7038
|
+
const lineStart = source.lastIndexOf("\n", at) + 1;
|
|
7039
|
+
return `${/^[ \t]*/.exec(source.slice(lineStart, at))?.[0] ?? ""} `;
|
|
7040
|
+
}
|
|
6034
7041
|
function unguardJs(source) {
|
|
6035
|
-
const
|
|
6036
|
-
|
|
7042
|
+
const checkpoints = uncheckpointJs(source);
|
|
7043
|
+
const imported = jsGuardImport(checkpoints.after);
|
|
7044
|
+
if (!imported) return checkpoints;
|
|
7045
|
+
source = checkpoints.after;
|
|
6037
7046
|
const { alias } = imported;
|
|
6038
7047
|
const lineOf = lineLocator2(source);
|
|
6039
7048
|
const edits = [];
|
|
6040
|
-
const leftovers = [];
|
|
7049
|
+
const leftovers = [...checkpoints.leftovers];
|
|
6041
7050
|
const calls = new RegExp(`(?<![\\w$.])${escapeForRegex(alias)}\\s*\\(`, "g");
|
|
6042
7051
|
for (const match of source.matchAll(calls)) {
|
|
6043
7052
|
const start = match.index ?? 0;
|
|
@@ -6076,14 +7085,62 @@ function unguardJs(source) {
|
|
|
6076
7085
|
}
|
|
6077
7086
|
let after = applyEdits(source, edits);
|
|
6078
7087
|
const stillUsed = new RegExp(`(?<![\\w$.])${escapeForRegex(alias)}\\s*\\(`).test(after);
|
|
6079
|
-
if (!stillUsed) after =
|
|
6080
|
-
return { after, removed: edits.length, leftovers };
|
|
7088
|
+
if (!stillUsed) after = removeJsSdkImport(after, "guard");
|
|
7089
|
+
return { after, removed: edits.length + checkpoints.removed, leftovers };
|
|
7090
|
+
}
|
|
7091
|
+
function uncheckpointJs(source) {
|
|
7092
|
+
const alias = existingImport(source, "checkpoint", "js");
|
|
7093
|
+
if (!alias) return { after: source, removed: 0, leftovers: [] };
|
|
7094
|
+
const lineOf = lineLocator2(source);
|
|
7095
|
+
const leftovers = [];
|
|
7096
|
+
let removed = 0;
|
|
7097
|
+
const key = `(?:(['"\`])[a-z][a-z0-9_]*\\1|[A-Za-z_$][\\w$]*(?:\\s*(?:\\?\\.|\\.)\\s*[A-Za-z_$][\\w$]*)+)`;
|
|
7098
|
+
let after = source.replace(
|
|
7099
|
+
new RegExp(
|
|
7100
|
+
`^[ \\t]*(?:await[ \\t]+)?${escapeForRegex(alias)}\\(\\s*${key}[^\\n]*\\)[ \\t]*;?[ \\t]*\\r?\\n`,
|
|
7101
|
+
"gm"
|
|
7102
|
+
),
|
|
7103
|
+
() => {
|
|
7104
|
+
removed += 1;
|
|
7105
|
+
return "";
|
|
7106
|
+
}
|
|
7107
|
+
);
|
|
7108
|
+
for (const match of after.matchAll(
|
|
7109
|
+
new RegExp(`(?<![\\w$.])${escapeForRegex(alias)}\\s*\\(`, "g")
|
|
7110
|
+
)) {
|
|
7111
|
+
leftovers.push({
|
|
7112
|
+
line: lineOf(match.index ?? 0),
|
|
7113
|
+
text: after.slice(match.index ?? 0, (match.index ?? 0) + 80).split("\n")[0] ?? ""
|
|
7114
|
+
});
|
|
7115
|
+
}
|
|
7116
|
+
if (leftovers.length === 0) after = removeJsSdkImport(after, "checkpoint");
|
|
7117
|
+
return { after, removed, leftovers };
|
|
6081
7118
|
}
|
|
6082
7119
|
function unguardPython(source) {
|
|
6083
7120
|
const imported = /^from[ \t]+veefore[ \t]+import[ \t]+([^\n]+)$/m.exec(source);
|
|
6084
7121
|
if (!imported) return { after: source, removed: 0, leftovers: [] };
|
|
6085
|
-
const
|
|
6086
|
-
|
|
7122
|
+
const names = (imported[1] ?? "").replace(/[()]/g, "").split(",");
|
|
7123
|
+
const spec = names.map((part) => /^guard(?:\s+as\s+(\w+))?$/.exec(part.trim())).find((found) => found !== null);
|
|
7124
|
+
const gate = names.map((part) => /^checkpoint(?:\s+as\s+(\w+))?$/.exec(part.trim())).find((found) => found !== null);
|
|
7125
|
+
if (!spec && !gate) return { after: source, removed: 0, leftovers: [] };
|
|
7126
|
+
if (!spec) {
|
|
7127
|
+
const alias2 = gate?.[1] ?? "checkpoint";
|
|
7128
|
+
let removed2 = 0;
|
|
7129
|
+
let after2 = source.replace(
|
|
7130
|
+
new RegExp(
|
|
7131
|
+
`^[ \\t]*${escapeForRegex(alias2)}\\(\\s*(['"])[a-z][a-z0-9_]*\\1[^\\n]*\\)[ \\t]*\\r?\\n`,
|
|
7132
|
+
"gm"
|
|
7133
|
+
),
|
|
7134
|
+
() => {
|
|
7135
|
+
removed2 += 1;
|
|
7136
|
+
return "";
|
|
7137
|
+
}
|
|
7138
|
+
);
|
|
7139
|
+
if (!new RegExp(`(?<![\\w.])${escapeForRegex(alias2)}\\s*\\(`).test(after2)) {
|
|
7140
|
+
after2 = removePythonImport(after2, alias2);
|
|
7141
|
+
}
|
|
7142
|
+
return { after: after2, removed: removed2, leftovers: [] };
|
|
7143
|
+
}
|
|
6087
7144
|
const alias = spec[1] ?? "guard";
|
|
6088
7145
|
const lineOf = lineLocator2(source);
|
|
6089
7146
|
let removed = 0;
|
|
@@ -6122,18 +7179,20 @@ function unguardPython(source) {
|
|
|
6122
7179
|
after = applyEdits(after, edits);
|
|
6123
7180
|
removed += edits.length;
|
|
6124
7181
|
const stillUsed = new RegExp(`(?<![\\w.])@?${escapeForRegex(alias)}\\s*\\(`).test(after);
|
|
6125
|
-
if (!stillUsed)
|
|
6126
|
-
after = after.replace(
|
|
6127
|
-
/^from[ \t]+veefore[ \t]+import[ \t]+([^\n]+)\r?\n?/m,
|
|
6128
|
-
(line, names) => {
|
|
6129
|
-
const rest = names.replace(/[()]/g, "").split(",").map((part) => part.trim()).filter((part) => part && !/^guard(?:\s+as\s+\w+)?$/.test(part));
|
|
6130
|
-
return rest.length === 0 ? "" : `from veefore import ${rest.join(", ")}
|
|
6131
|
-
`;
|
|
6132
|
-
}
|
|
6133
|
-
);
|
|
6134
|
-
}
|
|
7182
|
+
if (!stillUsed) after = removePythonImport(after, "guard");
|
|
6135
7183
|
return { after, removed, leftovers };
|
|
6136
7184
|
}
|
|
7185
|
+
function removePythonImport(source, name) {
|
|
7186
|
+
const target = new RegExp(`^${escapeForRegex(name)}(?:\\s+as\\s+\\w+)?$`);
|
|
7187
|
+
return source.replace(
|
|
7188
|
+
/^from[ \t]+veefore[ \t]+import[ \t]+([^\n]+)\r?\n?/m,
|
|
7189
|
+
(_line, names) => {
|
|
7190
|
+
const rest = names.replace(/[()]/g, "").split(",").map((part) => part.trim()).filter((part) => part && !target.test(part));
|
|
7191
|
+
return rest.length === 0 ? "" : `from veefore import ${rest.join(", ")}
|
|
7192
|
+
`;
|
|
7193
|
+
}
|
|
7194
|
+
);
|
|
7195
|
+
}
|
|
6137
7196
|
function arrowExpressionBody(fn) {
|
|
6138
7197
|
let cursor = 0;
|
|
6139
7198
|
if (fn.startsWith("(")) {
|
|
@@ -6168,29 +7227,28 @@ function jsGuardImport(source) {
|
|
|
6168
7227
|
}
|
|
6169
7228
|
return null;
|
|
6170
7229
|
}
|
|
6171
|
-
function
|
|
6172
|
-
const
|
|
6173
|
-
|
|
7230
|
+
function removeJsSdkImport(source, name) {
|
|
7231
|
+
const escaped = escapeForRegex(name);
|
|
7232
|
+
const drop = (names, rebuild, target) => {
|
|
7233
|
+
const rest = names.split(",").map((part) => part.trim()).filter((part) => part && !target.test(part));
|
|
6174
7234
|
return rest.length === 0 ? "" : rebuild(rest);
|
|
6175
7235
|
};
|
|
6176
7236
|
let after = source.replace(
|
|
6177
7237
|
/^[ \t]*import\s*\{([^}]*)\}\s*from\s*(['"])@veefore\/sdk\2\s*;?[ \t]*\r?\n?/m,
|
|
6178
|
-
(
|
|
6179
|
-
full,
|
|
7238
|
+
(_full, names, quote2) => drop(
|
|
6180
7239
|
names,
|
|
6181
7240
|
(rest) => `import { ${rest.join(", ")} } from ${quote2}@veefore/sdk${quote2};
|
|
6182
7241
|
`,
|
|
6183
|
-
|
|
7242
|
+
new RegExp(`^${escaped}(\\s+as\\s+[\\w$]+)?$`)
|
|
6184
7243
|
)
|
|
6185
7244
|
);
|
|
6186
7245
|
after = after.replace(
|
|
6187
7246
|
/^[ \t]*const\s*\{([^}]*)\}\s*=\s*require\(\s*(['"])@veefore\/sdk\2\s*\)\s*;?[ \t]*\r?\n?/m,
|
|
6188
|
-
(
|
|
6189
|
-
full,
|
|
7247
|
+
(_full, names, quote2) => drop(
|
|
6190
7248
|
names,
|
|
6191
7249
|
(rest) => `const { ${rest.join(", ")} } = require(${quote2}@veefore/sdk${quote2});
|
|
6192
7250
|
`,
|
|
6193
|
-
|
|
7251
|
+
new RegExp(`^${escaped}(\\s*:\\s*[\\w$]+)?$`)
|
|
6194
7252
|
)
|
|
6195
7253
|
);
|
|
6196
7254
|
return after;
|
|
@@ -6201,22 +7259,35 @@ function escapeForRegex(value) {
|
|
|
6201
7259
|
function isGuarded(value, alias) {
|
|
6202
7260
|
return new RegExp(`^\\s*(${alias}|guard|veeforeGuard)\\(`).test(value);
|
|
6203
7261
|
}
|
|
6204
|
-
|
|
6205
|
-
|
|
6206
|
-
var PY_GUARD_IMPORT = /^from\s+veefore\s+import\s+[^\n]*\bguard\b(?:\s+as\s+(\w+))?/m;
|
|
6207
|
-
function existingGuardImport(source, language) {
|
|
7262
|
+
function existingImport(source, name, language) {
|
|
7263
|
+
const escaped = escapeForRegex(name);
|
|
6208
7264
|
if (language === "py") {
|
|
6209
|
-
const match2 =
|
|
6210
|
-
|
|
6211
|
-
|
|
6212
|
-
|
|
6213
|
-
|
|
7265
|
+
const match2 = new RegExp(
|
|
7266
|
+
`^from\\s+veefore\\s+import\\s+[^\\n]*\\b${escaped}\\b(?:\\s+as\\s+(\\w+))?`,
|
|
7267
|
+
"m"
|
|
7268
|
+
).exec(source);
|
|
7269
|
+
return match2 ? match2[1] ?? name : null;
|
|
7270
|
+
}
|
|
7271
|
+
const esm = new RegExp(
|
|
7272
|
+
`import\\s*\\{[^}]*\\b${escaped}\\b(?:\\s+as\\s+([A-Za-z_$][\\w$]*))?[^}]*\\}\\s*from\\s*['"]@veefore/sdk['"]`
|
|
7273
|
+
).exec(source);
|
|
7274
|
+
const cjs = new RegExp(
|
|
7275
|
+
`\\{[^}]*\\b${escaped}\\b(?:\\s*:\\s*([A-Za-z_$][\\w$]*))?[^}]*\\}\\s*=\\s*require\\(\\s*['"]@veefore/sdk['"]\\s*\\)`
|
|
7276
|
+
).exec(source);
|
|
7277
|
+
const match = esm ?? cjs;
|
|
7278
|
+
return match ? match[1] ?? name : null;
|
|
6214
7279
|
}
|
|
6215
|
-
function
|
|
6216
|
-
|
|
7280
|
+
function existingGuardImport(source, language) {
|
|
7281
|
+
return existingImport(source, "guard", language);
|
|
7282
|
+
}
|
|
7283
|
+
function chooseAliasFor(source, name, language) {
|
|
7284
|
+
const existing = existingImport(source, name, language);
|
|
6217
7285
|
if (existing) return existing;
|
|
6218
|
-
if (
|
|
6219
|
-
return language === "py" ?
|
|
7286
|
+
if (!new RegExp(`\\b${escapeForRegex(name)}\\b`).test(source)) return name;
|
|
7287
|
+
return language === "py" ? `veefore_${name}` : `veefore${name[0]?.toUpperCase() ?? ""}${name.slice(1)}`;
|
|
7288
|
+
}
|
|
7289
|
+
function chooseAlias(source, language) {
|
|
7290
|
+
return chooseAliasFor(source, "guard", language);
|
|
6220
7291
|
}
|
|
6221
7292
|
function applyEdits(source, edits) {
|
|
6222
7293
|
let out2 = source;
|
|
@@ -6225,14 +7296,22 @@ function applyEdits(source, edits) {
|
|
|
6225
7296
|
}
|
|
6226
7297
|
return out2;
|
|
6227
7298
|
}
|
|
6228
|
-
|
|
7299
|
+
var SCHEMA_ONLY_REASON = "this is only the schema; guard the code that runs the tool the model picked (often one dispatch function for every tool)";
|
|
7300
|
+
function schemaOnly(source, python, wanted, placed = /* @__PURE__ */ new Set()) {
|
|
6229
7301
|
const lineOf = lineLocator2(source);
|
|
6230
|
-
|
|
6231
|
-
|
|
6232
|
-
|
|
6233
|
-
|
|
6234
|
-
|
|
6235
|
-
|
|
7302
|
+
const hits = [
|
|
7303
|
+
...findToolSchemas(source, python).map((hit) => ({ name: hit.name, index: hit.index })),
|
|
7304
|
+
...python ? [] : findFunctionToolSchemas(source).map((hit) => ({ name: hit.name, index: hit.index }))
|
|
7305
|
+
].sort((a, b) => a.index - b.index);
|
|
7306
|
+
const changes = [];
|
|
7307
|
+
const seen = new Set(placed);
|
|
7308
|
+
for (const hit of hits) {
|
|
7309
|
+
const key = toToolKey(hit.name);
|
|
7310
|
+
if (!wanted.has(key) || seen.has(key)) continue;
|
|
7311
|
+
seen.add(key);
|
|
7312
|
+
changes.push({ key, line: lineOf(hit.index), status: "manual", reason: SCHEMA_ONLY_REASON });
|
|
7313
|
+
}
|
|
7314
|
+
return changes;
|
|
6236
7315
|
}
|
|
6237
7316
|
function dedupeChanges(changes) {
|
|
6238
7317
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -6352,10 +7431,55 @@ function myers(a, b) {
|
|
|
6352
7431
|
|
|
6353
7432
|
// src/integrate.ts
|
|
6354
7433
|
async function integrate(root, toolKeys, options) {
|
|
6355
|
-
const
|
|
7434
|
+
const index = indexFiles(root);
|
|
7435
|
+
const anchors = [...toolKeyAnchors(toolKeys), ...TOOL_ANCHORS, ...GUARD_ANCHORS];
|
|
7436
|
+
const candidates = shortlist(root, index, anchors);
|
|
7437
|
+
const sourceFiles = candidates.files;
|
|
7438
|
+
const truncated = index.truncated || candidates.truncated;
|
|
6356
7439
|
const interactive = options.interactive ?? (process.stdin.isTTY === true && process.stdout.isTTY === true && !options.yes);
|
|
6357
|
-
|
|
7440
|
+
if (truncated && !options.quiet) {
|
|
7441
|
+
out(
|
|
7442
|
+
`${symbol.warn} The search ran out of time before it finished, so a tool may be reported as not found, or the place it runs may be missed.`
|
|
7443
|
+
);
|
|
7444
|
+
}
|
|
7445
|
+
const sources = readSources(root, sourceFiles);
|
|
7446
|
+
const dispatchGuardFiles = findDispatchGuards(sources);
|
|
6358
7447
|
const dispatchGuarded = dispatchGuardFiles.length > 0;
|
|
7448
|
+
const alreadyGuarded = guardedToolKeys(sources, toolKeys);
|
|
7449
|
+
const reconcile = (result) => {
|
|
7450
|
+
for (const file of result.files) {
|
|
7451
|
+
for (const change of file.changes) {
|
|
7452
|
+
if (change.status === "manual" && alreadyGuarded.has(change.key)) {
|
|
7453
|
+
change.status = "already";
|
|
7454
|
+
delete change.reason;
|
|
7455
|
+
}
|
|
7456
|
+
}
|
|
7457
|
+
}
|
|
7458
|
+
return result;
|
|
7459
|
+
};
|
|
7460
|
+
let plan = reconcile(planRewrite(root, sourceFiles, toolKeys));
|
|
7461
|
+
const changeable = [
|
|
7462
|
+
...new Set(
|
|
7463
|
+
plan.files.flatMap(
|
|
7464
|
+
(file) => file.changes.filter((change) => change.status !== "already").map((change) => change.key)
|
|
7465
|
+
)
|
|
7466
|
+
)
|
|
7467
|
+
];
|
|
7468
|
+
const selected = await chooseTools(changeable, plan, {
|
|
7469
|
+
requested: options.tools,
|
|
7470
|
+
interactive,
|
|
7471
|
+
quiet: options.quiet === true
|
|
7472
|
+
});
|
|
7473
|
+
const skipped = changeable.filter((key) => !selected.includes(key));
|
|
7474
|
+
if (skipped.length > 0) {
|
|
7475
|
+
plan = reconcile(
|
|
7476
|
+
planRewrite(
|
|
7477
|
+
root,
|
|
7478
|
+
sourceFiles,
|
|
7479
|
+
toolKeys.filter((key) => !skipped.includes(key))
|
|
7480
|
+
)
|
|
7481
|
+
);
|
|
7482
|
+
}
|
|
6359
7483
|
let manual = plan.files.flatMap(
|
|
6360
7484
|
(file) => file.changes.filter((change) => change.status === "manual").map((change) => ({
|
|
6361
7485
|
file: file.file,
|
|
@@ -6379,7 +7503,7 @@ async function integrate(root, toolKeys, options) {
|
|
|
6379
7503
|
}
|
|
6380
7504
|
dispatch = [];
|
|
6381
7505
|
} else if (dispatch.length > 0) {
|
|
6382
|
-
dispatchPlan = planDispatch(
|
|
7506
|
+
dispatchPlan = planDispatch(sourceFiles, (file) => currentText(root, plan, file), [
|
|
6383
7507
|
...new Set(dispatch.map((item) => item.key))
|
|
6384
7508
|
]);
|
|
6385
7509
|
if (dispatchPlan?.after) {
|
|
@@ -6387,7 +7511,22 @@ async function integrate(root, toolKeys, options) {
|
|
|
6387
7511
|
dispatch = [];
|
|
6388
7512
|
}
|
|
6389
7513
|
}
|
|
6390
|
-
|
|
7514
|
+
if (dispatch.length > 0 && checkpointUsable(root, options.quiet === true)) {
|
|
7515
|
+
const placed = applyCheckpoints(
|
|
7516
|
+
plan,
|
|
7517
|
+
planCheckpoints(sourceFiles, (file) => currentText(root, plan, file), [
|
|
7518
|
+
...new Set(dispatch.map((item) => item.key))
|
|
7519
|
+
])
|
|
7520
|
+
);
|
|
7521
|
+
dispatch = dispatch.filter((item) => !placed.has(item.key));
|
|
7522
|
+
}
|
|
7523
|
+
const runsElsewhere = (key) => {
|
|
7524
|
+
const site = dispatchPlan?.sites?.get(key);
|
|
7525
|
+
return site !== void 0 && site.file !== dispatchPlan?.file;
|
|
7526
|
+
};
|
|
7527
|
+
const dispatchKeys = [
|
|
7528
|
+
...new Set(dispatch.filter((item) => !runsElsewhere(item.key)).map((item) => item.key))
|
|
7529
|
+
];
|
|
6391
7530
|
const dispatchItems = dispatchPlan ? dispatchKeys.map((key) => ({
|
|
6392
7531
|
file: dispatchPlan?.file,
|
|
6393
7532
|
key,
|
|
@@ -6395,7 +7534,7 @@ async function integrate(root, toolKeys, options) {
|
|
|
6395
7534
|
reason: `guard the code that runs the tool: ${dispatchPlan?.reason ?? ""}`,
|
|
6396
7535
|
language: languageOf(dispatchPlan?.file)
|
|
6397
7536
|
})) : [];
|
|
6398
|
-
const unplaced = dispatchPlan ?
|
|
7537
|
+
const unplaced = dispatchPlan ? dispatch.filter((item) => runsElsewhere(item.key)) : dispatch;
|
|
6399
7538
|
const pending = [...manual, ...dispatchItems];
|
|
6400
7539
|
if (pending.length > 0 && !options.dryRun) {
|
|
6401
7540
|
const hostedAvailable = await hostedEnabled(options.api ?? null);
|
|
@@ -6450,8 +7589,9 @@ async function integrate(root, toolKeys, options) {
|
|
|
6450
7589
|
const entry = plan.files.find((file) => file.file === proposal.file);
|
|
6451
7590
|
if (!entry) continue;
|
|
6452
7591
|
entry.after = proposal.proposal;
|
|
7592
|
+
const guarded = findGuardedKeys([{ file: proposal.file, text: proposal.proposal }]);
|
|
6453
7593
|
for (const change of entry.changes) {
|
|
6454
|
-
if (change.status === "manual" &&
|
|
7594
|
+
if (change.status === "manual" && guarded.has(change.key)) {
|
|
6455
7595
|
change.status = "wrapped";
|
|
6456
7596
|
change.via = "ai";
|
|
6457
7597
|
delete change.reason;
|
|
@@ -6465,29 +7605,46 @@ async function integrate(root, toolKeys, options) {
|
|
|
6465
7605
|
dispatch = remaining.filter(isDispatchItem);
|
|
6466
7606
|
if (method !== "skip" && !options.quiet) {
|
|
6467
7607
|
if (manual.length > 0) printManualInstructions(manual);
|
|
6468
|
-
|
|
7608
|
+
printDispatchInstructions([...dispatch, ...unplaced], dispatchPlan);
|
|
6469
7609
|
}
|
|
6470
7610
|
} else if (pending.length > 0 && !options.quiet) {
|
|
6471
|
-
|
|
7611
|
+
printDispatchInstructions([...dispatchItems, ...unplaced], dispatchPlan);
|
|
6472
7612
|
dispatch = dispatchItems;
|
|
7613
|
+
} else if (unplaced.length > 0 && !options.quiet && options.assist !== "skip") {
|
|
7614
|
+
printDispatchInstructions(unplaced, dispatchPlan);
|
|
6473
7615
|
}
|
|
6474
|
-
|
|
6475
|
-
printDispatchInstructions(unplaced, null);
|
|
6476
|
-
}
|
|
6477
|
-
manual = [...manual, ...dispatch, ...unplaced];
|
|
7616
|
+
manual = stillManual([...manual, ...dispatch, ...unplaced], plan);
|
|
6478
7617
|
const toWrite = plan.files.filter((file) => file.before !== file.after);
|
|
6479
7618
|
const languages = new Set(toWrite.map((file) => file.file.endsWith(".py") ? "py" : "js"));
|
|
6480
7619
|
if (!options.quiet) printPlan(plan);
|
|
6481
7620
|
if (toWrite.length === 0) {
|
|
6482
|
-
|
|
7621
|
+
const quiet = options.quiet === true || options.dryRun === true;
|
|
7622
|
+
if (!quiet) {
|
|
6483
7623
|
const guardedFiles = plan.files.filter((file) => file.changes.some((change) => change.status !== "manual")).map((file) => file.file);
|
|
6484
7624
|
reportSdkReadiness(root, [.../* @__PURE__ */ new Set([...guardedFiles, ...dispatchGuardFiles])]);
|
|
6485
7625
|
}
|
|
6486
|
-
|
|
7626
|
+
const unguarded2 = reportProtection(root, sourceFiles, toolKeys, quiet);
|
|
7627
|
+
return {
|
|
7628
|
+
status: "nothing-to-do",
|
|
7629
|
+
plan,
|
|
7630
|
+
checks: [],
|
|
7631
|
+
backupId: null,
|
|
7632
|
+
manual,
|
|
7633
|
+
unguarded: unguarded2,
|
|
7634
|
+
skipped
|
|
7635
|
+
};
|
|
6487
7636
|
}
|
|
6488
7637
|
if (options.dryRun) {
|
|
6489
7638
|
out(style.grey("Dry run: nothing was written."));
|
|
6490
|
-
return {
|
|
7639
|
+
return {
|
|
7640
|
+
status: "dry-run",
|
|
7641
|
+
plan,
|
|
7642
|
+
checks: [],
|
|
7643
|
+
backupId: null,
|
|
7644
|
+
manual,
|
|
7645
|
+
unguarded: reportProtection(root, sourceFiles, toolKeys, true),
|
|
7646
|
+
skipped
|
|
7647
|
+
};
|
|
6491
7648
|
}
|
|
6492
7649
|
if (!options.quiet) {
|
|
6493
7650
|
reportSdkReadiness(
|
|
@@ -6501,7 +7658,15 @@ async function integrate(root, toolKeys, options) {
|
|
|
6501
7658
|
);
|
|
6502
7659
|
if (!proceed) {
|
|
6503
7660
|
out(style.grey("No files were changed. Run `veefore integrate` when you are ready."));
|
|
6504
|
-
return {
|
|
7661
|
+
return {
|
|
7662
|
+
status: "declined",
|
|
7663
|
+
plan,
|
|
7664
|
+
checks: [],
|
|
7665
|
+
backupId: null,
|
|
7666
|
+
manual,
|
|
7667
|
+
unguarded: reportProtection(root, sourceFiles, toolKeys, true),
|
|
7668
|
+
skipped
|
|
7669
|
+
};
|
|
6505
7670
|
}
|
|
6506
7671
|
const jsReady = languages.has("js") && sdkInstalled(root, "js");
|
|
6507
7672
|
const baseline = jsReady ? typecheck(
|
|
@@ -6509,13 +7674,22 @@ async function integrate(root, toolKeys, options) {
|
|
|
6509
7674
|
toWrite.map((file) => file.file)
|
|
6510
7675
|
) : null;
|
|
6511
7676
|
for (const file of toWrite) {
|
|
6512
|
-
if (
|
|
7677
|
+
if (readFileSync9(path10.join(root, file.file), "utf8") !== file.before) {
|
|
6513
7678
|
out(`${symbol.fail} ${file.file} changed while setup was running. Nothing was written.`);
|
|
6514
|
-
return {
|
|
7679
|
+
return {
|
|
7680
|
+
status: "declined",
|
|
7681
|
+
plan,
|
|
7682
|
+
checks: [],
|
|
7683
|
+
backupId: null,
|
|
7684
|
+
manual,
|
|
7685
|
+
unguarded: reportProtection(root, sourceFiles, toolKeys, true),
|
|
7686
|
+
skipped
|
|
7687
|
+
};
|
|
6515
7688
|
}
|
|
6516
7689
|
}
|
|
6517
7690
|
const backup = createBackup(root, toWrite);
|
|
6518
|
-
for (const file of toWrite) writeFilePreservingMode(
|
|
7691
|
+
for (const file of toWrite) writeFilePreservingMode(path10.join(root, file.file), file.after);
|
|
7692
|
+
forgetIndex(root);
|
|
6519
7693
|
out(
|
|
6520
7694
|
`${symbol.pass} Updated ${toWrite.length} file${toWrite.length === 1 ? "" : "s"} ${style.grey(`(backup ${backup.id})`)}`
|
|
6521
7695
|
);
|
|
@@ -6571,7 +7745,15 @@ async function integrate(root, toolKeys, options) {
|
|
|
6571
7745
|
`${symbol.warn} A check failed after the change, so every file was put back as it was ${style.grey(`(${restored.restored.length} restored)`)}.`
|
|
6572
7746
|
);
|
|
6573
7747
|
out(style.grey(" Please report this: the rewrite should never break a project."));
|
|
6574
|
-
return {
|
|
7748
|
+
return {
|
|
7749
|
+
status: "rolled-back",
|
|
7750
|
+
plan,
|
|
7751
|
+
checks,
|
|
7752
|
+
backupId: null,
|
|
7753
|
+
manual,
|
|
7754
|
+
unguarded: reportProtection(root, sourceFiles, toolKeys, true),
|
|
7755
|
+
skipped
|
|
7756
|
+
};
|
|
6575
7757
|
}
|
|
6576
7758
|
for (const language of languages) {
|
|
6577
7759
|
if (!sdkInstalled(root, language)) {
|
|
@@ -6580,13 +7762,39 @@ async function integrate(root, toolKeys, options) {
|
|
|
6580
7762
|
);
|
|
6581
7763
|
}
|
|
6582
7764
|
}
|
|
7765
|
+
if (skipped.length > 0 && !options.quiet) {
|
|
7766
|
+
out(
|
|
7767
|
+
`${symbol.info} Left out of this run, as you chose: ${style.bold(skipped.join(", "))} ${style.grey("(no file was changed for these)")}`
|
|
7768
|
+
);
|
|
7769
|
+
}
|
|
7770
|
+
const unguarded = reportProtection(root, sourceFiles, toolKeys, options.quiet === true);
|
|
6583
7771
|
out(style.grey(" Undo at any time with `veefore undo`."));
|
|
6584
|
-
return { status: "applied", plan, checks, backupId: backup.id, manual };
|
|
7772
|
+
return { status: "applied", plan, checks, backupId: backup.id, manual, unguarded, skipped };
|
|
7773
|
+
}
|
|
7774
|
+
function reportProtection(root, files, toolKeys, quiet) {
|
|
7775
|
+
const guarded = guardedToolKeys(readSources(root, files), toolKeys);
|
|
7776
|
+
const unguarded = toolKeys.filter((key) => !guarded.has(key));
|
|
7777
|
+
if (quiet) return unguarded;
|
|
7778
|
+
if (unguarded.length === 0) {
|
|
7779
|
+
out(
|
|
7780
|
+
`${symbol.pass} Every registered tool goes through Veefore ${style.grey(`(${toolKeys.length})`)}`
|
|
7781
|
+
);
|
|
7782
|
+
return unguarded;
|
|
7783
|
+
}
|
|
7784
|
+
out(
|
|
7785
|
+
`${symbol.warn} ${toolKeys.length - unguarded.length} of ${toolKeys.length} registered tools go through Veefore. Still unprotected: ${style.bold(unguarded.join(", "))}`
|
|
7786
|
+
);
|
|
7787
|
+
out(
|
|
7788
|
+
style.grey(
|
|
7789
|
+
" The instructions above say what each one needs. Then `veefore integrate --check` to confirm."
|
|
7790
|
+
)
|
|
7791
|
+
);
|
|
7792
|
+
return unguarded;
|
|
6585
7793
|
}
|
|
6586
7794
|
function reportSdkReadiness(root, files) {
|
|
6587
7795
|
const problems = [];
|
|
6588
7796
|
if (files.length === 0) return problems;
|
|
6589
|
-
const dirs = [...new Set(files.map((file) =>
|
|
7797
|
+
const dirs = [...new Set(files.map((file) => path10.dirname(file)))];
|
|
6590
7798
|
const python = files.some((file) => file.endsWith(".py"));
|
|
6591
7799
|
const sdks = installedSdks(root, { dirs, python });
|
|
6592
7800
|
for (const sdk of sdks) {
|
|
@@ -6663,7 +7871,7 @@ async function removeGuards(root, options) {
|
|
|
6663
7871
|
const tsFiles = changed.filter((file) => /\.(m|c)?tsx?$/.test(file));
|
|
6664
7872
|
const baseline = tsFiles.length > 0 ? typecheck(root, tsFiles) : null;
|
|
6665
7873
|
const backup = createBackup(root, toWrite);
|
|
6666
|
-
for (const entry of toWrite) writeFilePreservingMode(
|
|
7874
|
+
for (const entry of toWrite) writeFilePreservingMode(path10.join(root, entry.file), entry.after);
|
|
6667
7875
|
const checks = [];
|
|
6668
7876
|
const py = changed.filter((file) => file.endsWith(".py"));
|
|
6669
7877
|
if (py.length > 0) checks.push(pythonSyntax(root, py));
|
|
@@ -6687,7 +7895,7 @@ function currentText(root, plan, file) {
|
|
|
6687
7895
|
const entry = plan.files.find((candidate) => candidate.file === file);
|
|
6688
7896
|
if (entry) return entry.after;
|
|
6689
7897
|
try {
|
|
6690
|
-
return
|
|
7898
|
+
return readFileSync9(path10.join(root, file), "utf8");
|
|
6691
7899
|
} catch {
|
|
6692
7900
|
return null;
|
|
6693
7901
|
}
|
|
@@ -6709,17 +7917,113 @@ function applyDispatch(root, plan, file, after, line, items, via) {
|
|
|
6709
7917
|
entry.changes.push({ key, line, status: "wrapped", ...via ? { via } : {} });
|
|
6710
7918
|
}
|
|
6711
7919
|
}
|
|
6712
|
-
function
|
|
6713
|
-
|
|
7920
|
+
function stillManual(items, plan) {
|
|
7921
|
+
const open = new Set(
|
|
7922
|
+
plan.files.flatMap(
|
|
7923
|
+
(file) => file.changes.filter((change) => change.status === "manual").map((change) => change.key)
|
|
7924
|
+
)
|
|
7925
|
+
);
|
|
7926
|
+
const seen = /* @__PURE__ */ new Set();
|
|
7927
|
+
return items.filter((item) => {
|
|
7928
|
+
if (!open.has(item.key)) return false;
|
|
7929
|
+
const id = isDispatchItem(item) ? item.key : `${item.key}\0${item.file}`;
|
|
7930
|
+
if (seen.has(id)) return false;
|
|
7931
|
+
seen.add(id);
|
|
7932
|
+
return true;
|
|
7933
|
+
});
|
|
7934
|
+
}
|
|
7935
|
+
async function chooseTools(changeable, plan, options) {
|
|
7936
|
+
if (options.requested) {
|
|
7937
|
+
const asked = new Set(options.requested);
|
|
7938
|
+
const unknown = options.requested.filter((key) => !changeable.includes(key));
|
|
7939
|
+
if (unknown.length > 0 && !options.quiet) {
|
|
7940
|
+
out(
|
|
7941
|
+
`${symbol.warn} Nothing to change for: ${unknown.join(", ")} ${style.grey("(already protected, or not found in the code)")}`
|
|
7942
|
+
);
|
|
7943
|
+
}
|
|
7944
|
+
return changeable.filter((key) => asked.has(key));
|
|
7945
|
+
}
|
|
7946
|
+
if (!options.interactive || changeable.length < 2) return changeable;
|
|
7947
|
+
const where = /* @__PURE__ */ new Map();
|
|
7948
|
+
for (const file of plan.files) {
|
|
7949
|
+
for (const change of file.changes) {
|
|
7950
|
+
if (change.status === "already" || where.has(change.key)) continue;
|
|
7951
|
+
where.set(change.key, { line: change.line, file: file.file, status: change.status });
|
|
7952
|
+
}
|
|
7953
|
+
}
|
|
7954
|
+
try {
|
|
7955
|
+
return await selectMany(
|
|
7956
|
+
"Which tools should Veefore decide for?",
|
|
7957
|
+
changeable.map((key) => {
|
|
7958
|
+
const at = where.get(key);
|
|
7959
|
+
return {
|
|
7960
|
+
label: key,
|
|
7961
|
+
value: key,
|
|
7962
|
+
hint: at ? `${at.status === "wrapped" ? "guard here" : "needs a change"} \xB7 ${at.file}:${at.line}` : ""
|
|
7963
|
+
};
|
|
7964
|
+
}),
|
|
7965
|
+
{ help: "All of them unless you say otherwise. Only what is ticked gets changed." }
|
|
7966
|
+
);
|
|
7967
|
+
} catch (error) {
|
|
7968
|
+
if (error instanceof MenuCancelled) return [];
|
|
7969
|
+
throw error;
|
|
7970
|
+
}
|
|
7971
|
+
}
|
|
7972
|
+
function checkpointUsable(root, quiet) {
|
|
7973
|
+
const sdks = installedSdks(root).filter((sdk) => sdk.language === "js");
|
|
7974
|
+
const stale = sdks.filter((sdk) => !sdk.hasCheckpoint);
|
|
7975
|
+
if (sdks.length === 0 || stale.length === 0) return true;
|
|
7976
|
+
if (!quiet) {
|
|
7977
|
+
out(
|
|
7978
|
+
`${symbol.warn} Tools your code runs in a branch rather than a function are guarded with ${style.bold("checkpoint()")}, added in @veefore/sdk ${MIN_CHECKPOINT_SDK}. ${stale[0]?.version ?? "The installed version"} does not have it, so they are left for you.`
|
|
7979
|
+
);
|
|
7980
|
+
out(
|
|
7981
|
+
` Upgrade and re-run: ${style.bold(`${installCommand(root, "js")}@^${MIN_CHECKPOINT_SDK}`)}`
|
|
7982
|
+
);
|
|
7983
|
+
}
|
|
7984
|
+
return false;
|
|
7985
|
+
}
|
|
7986
|
+
function applyCheckpoints(plan, plans) {
|
|
7987
|
+
const placed = /* @__PURE__ */ new Set();
|
|
7988
|
+
for (const checkpoint of plans) {
|
|
7989
|
+
let entry = plan.files.find((candidate) => candidate.file === checkpoint.file);
|
|
7990
|
+
if (!entry) {
|
|
7991
|
+
entry = {
|
|
7992
|
+
file: checkpoint.file,
|
|
7993
|
+
before: checkpoint.before,
|
|
7994
|
+
after: checkpoint.after,
|
|
7995
|
+
changes: []
|
|
7996
|
+
};
|
|
7997
|
+
plan.files.push(entry);
|
|
7998
|
+
}
|
|
7999
|
+
entry.after = checkpoint.after;
|
|
8000
|
+
for (const place of checkpoint.placed) {
|
|
8001
|
+
placed.add(place.key);
|
|
8002
|
+
entry.changes.push({
|
|
8003
|
+
key: place.key,
|
|
8004
|
+
line: place.line,
|
|
8005
|
+
status: "wrapped",
|
|
8006
|
+
via: "checkpoint"
|
|
8007
|
+
});
|
|
8008
|
+
}
|
|
8009
|
+
}
|
|
8010
|
+
for (const file of plan.files) {
|
|
8011
|
+
file.changes = file.changes.filter(
|
|
8012
|
+
(change) => !(change.status === "manual" && placed.has(change.key) && isDispatchItem({ reason: change.reason ?? "" }))
|
|
8013
|
+
);
|
|
8014
|
+
}
|
|
8015
|
+
return placed;
|
|
8016
|
+
}
|
|
8017
|
+
function readSources(root, files = listSourceFiles(root)) {
|
|
8018
|
+
return files.flatMap((file) => {
|
|
6714
8019
|
try {
|
|
6715
|
-
return [{ file, text:
|
|
8020
|
+
return [{ file, text: readFileSync9(path10.join(root, file), "utf8") }];
|
|
6716
8021
|
} catch {
|
|
6717
8022
|
return [];
|
|
6718
8023
|
}
|
|
6719
8024
|
});
|
|
6720
8025
|
}
|
|
6721
|
-
function
|
|
6722
|
-
const sources = readSources(root);
|
|
8026
|
+
function guardedToolKeys(sources, toolKeys) {
|
|
6723
8027
|
const guarded = findGuardedKeys(sources);
|
|
6724
8028
|
const dispatchFiles = findDispatchGuards(sources);
|
|
6725
8029
|
if (dispatchFiles.length > 0) {
|
|
@@ -6734,6 +8038,16 @@ function checkIntegration(root, toolKeys) {
|
|
|
6734
8038
|
}
|
|
6735
8039
|
}
|
|
6736
8040
|
}
|
|
8041
|
+
return new Map([...guarded].filter(([key]) => toolKeys.includes(key)));
|
|
8042
|
+
}
|
|
8043
|
+
function checkIntegration(root, toolKeys) {
|
|
8044
|
+
const index = indexFiles(root);
|
|
8045
|
+
const candidates = shortlist(root, index, [
|
|
8046
|
+
...toolKeyAnchors(toolKeys),
|
|
8047
|
+
...TOOL_ANCHORS,
|
|
8048
|
+
...GUARD_ANCHORS
|
|
8049
|
+
]);
|
|
8050
|
+
const guarded = guardedToolKeys(readSources(root, candidates.files), toolKeys);
|
|
6737
8051
|
heading("Protection check");
|
|
6738
8052
|
for (const key of toolKeys) {
|
|
6739
8053
|
const file = guarded.get(key);
|
|
@@ -6796,22 +8110,31 @@ function printPlan(plan) {
|
|
|
6796
8110
|
const wrapped = all.filter((entry) => entry.change.status === "wrapped");
|
|
6797
8111
|
const already = all.filter((entry) => entry.change.status === "already");
|
|
6798
8112
|
const manual = all.filter((entry) => entry.change.status === "manual");
|
|
8113
|
+
const tools = (entries) => new Set(entries.map((e) => e.change.key)).size;
|
|
8114
|
+
const keys = (entries) => [...new Set(entries.map((e) => e.change.key))];
|
|
6799
8115
|
if (wrapped.length > 0) {
|
|
8116
|
+
const count = tools(wrapped);
|
|
8117
|
+
const only = new Set(wrapped.map((entry) => entry.change.via ?? "guard"));
|
|
8118
|
+
const how = only.size === 1 && only.has("checkpoint") ? `gated with ${style.bold("checkpoint()")} where your code runs them` : `wrapped with ${style.bold("guard()")}`;
|
|
6800
8119
|
out(
|
|
6801
|
-
`${symbol.pass} ${
|
|
8120
|
+
`${symbol.pass} ${count} tool${count === 1 ? "" : "s"} will be ${how}, so Veefore decides each call:`
|
|
6802
8121
|
);
|
|
8122
|
+
const note = {
|
|
8123
|
+
ai: style.amber("proposed by AI \u2014 review the diff below"),
|
|
8124
|
+
checkpoint: style.grey("guarded where your code runs it")
|
|
8125
|
+
};
|
|
6803
8126
|
for (const { file, change } of wrapped) {
|
|
6804
8127
|
out(
|
|
6805
|
-
` ${style.bold(change.key.padEnd(24))} ${style.grey(`${file}:${change.line}`)}` + (change.via
|
|
8128
|
+
` ${style.bold(change.key.padEnd(24))} ${style.grey(`${file}:${change.line}`)}` + (change.via ? ` ${note[change.via] ?? ""}` : "")
|
|
6806
8129
|
);
|
|
6807
8130
|
}
|
|
6808
8131
|
}
|
|
6809
8132
|
if (already.length > 0) {
|
|
6810
|
-
out(`${symbol.pass} Already protected: ${already
|
|
8133
|
+
out(`${symbol.pass} Already protected: ${keys(already).join(", ")}`);
|
|
6811
8134
|
}
|
|
6812
8135
|
if (manual.length > 0) {
|
|
6813
8136
|
out(
|
|
6814
|
-
`${symbol.warn} Not changed automatically: ${manual
|
|
8137
|
+
`${symbol.warn} Not changed automatically: ${keys(manual).join(", ")} ${style.grey("(see instructions above)")}`
|
|
6815
8138
|
);
|
|
6816
8139
|
}
|
|
6817
8140
|
if (plan.notFound.length > 0) {
|
|
@@ -6833,8 +8156,8 @@ function printPlan(plan) {
|
|
|
6833
8156
|
}
|
|
6834
8157
|
|
|
6835
8158
|
// src/commands/remove.ts
|
|
6836
|
-
import { rmSync as
|
|
6837
|
-
import
|
|
8159
|
+
import { rmSync as rmSync4 } from "node:fs";
|
|
8160
|
+
import path11 from "node:path";
|
|
6838
8161
|
|
|
6839
8162
|
// src/discover.ts
|
|
6840
8163
|
var LOCAL_CANDIDATE_PORTS = [4e3, 4400, 4100, 4200, 4300, 4500, 8e3, 8080, 3e3];
|
|
@@ -6948,7 +8271,7 @@ async function removeCommand(args, flags, known) {
|
|
|
6948
8271
|
if (!file || !config) {
|
|
6949
8272
|
throw new CliError("This project is not connected to Veefore.", "There is nothing to remove.");
|
|
6950
8273
|
}
|
|
6951
|
-
const root =
|
|
8274
|
+
const root = path11.dirname(file);
|
|
6952
8275
|
const state = known ?? await agentState(config);
|
|
6953
8276
|
heading("Remove Veefore from this project");
|
|
6954
8277
|
details([
|
|
@@ -6999,7 +8322,7 @@ async function removeCommand(args, flags, known) {
|
|
|
6999
8322
|
out(style.grey("The project stays connected."));
|
|
7000
8323
|
return 0;
|
|
7001
8324
|
}
|
|
7002
|
-
|
|
8325
|
+
rmSync4(file);
|
|
7003
8326
|
const envRemoved = removeLocalEnvBlock(root);
|
|
7004
8327
|
out(
|
|
7005
8328
|
`${symbol.pass} Disconnected: removed veefore.config.json${envRemoved ? " and the Veefore block in .env.local" : ""}.`
|
|
@@ -7026,10 +8349,20 @@ async function integrateCommand(args, flags) {
|
|
|
7026
8349
|
let assist;
|
|
7027
8350
|
let dryRun = false;
|
|
7028
8351
|
let check = false;
|
|
8352
|
+
let tools;
|
|
7029
8353
|
for (const arg of args) {
|
|
7030
8354
|
if (arg === "--dry-run") dryRun = true;
|
|
7031
8355
|
else if (arg === "--check") check = true;
|
|
7032
|
-
else if (arg
|
|
8356
|
+
else if (arg === "--tools=all") tools = void 0;
|
|
8357
|
+
else if (arg.startsWith("--tools=")) {
|
|
8358
|
+
tools = arg.slice("--tools=".length).split(",").map((key) => key.trim()).filter((key) => key !== "");
|
|
8359
|
+
if (tools.length === 0) {
|
|
8360
|
+
throw new CliError(
|
|
8361
|
+
"`--tools` takes a comma-separated list of tool keys, or `all`.",
|
|
8362
|
+
"For example: --tools=refund_order,adjust_stock"
|
|
8363
|
+
);
|
|
8364
|
+
}
|
|
8365
|
+
} else if (arg.startsWith("--assist=")) {
|
|
7033
8366
|
const value = ASSIST_VALUES[arg.slice("--assist=".length)];
|
|
7034
8367
|
if (!value) {
|
|
7035
8368
|
throw new CliError(
|
|
@@ -7072,7 +8405,8 @@ async function integrateCommand(args, flags) {
|
|
|
7072
8405
|
dryRun,
|
|
7073
8406
|
quiet: flags.json,
|
|
7074
8407
|
assist,
|
|
7075
|
-
api
|
|
8408
|
+
api,
|
|
8409
|
+
...tools ? { tools } : {}
|
|
7076
8410
|
});
|
|
7077
8411
|
if (flags.json) {
|
|
7078
8412
|
out(
|
|
@@ -7083,6 +8417,8 @@ async function integrateCommand(args, flags) {
|
|
|
7083
8417
|
files: outcome.plan.files.map((file) => ({ file: file.file, changes: file.changes })),
|
|
7084
8418
|
not_found: outcome.plan.notFound,
|
|
7085
8419
|
manual: outcome.manual,
|
|
8420
|
+
unguarded: outcome.unguarded,
|
|
8421
|
+
skipped: outcome.skipped,
|
|
7086
8422
|
checks: outcome.checks
|
|
7087
8423
|
},
|
|
7088
8424
|
null,
|
|
@@ -7482,10 +8818,10 @@ async function resolveAuthorizedTools(context, filter) {
|
|
|
7482
8818
|
|
|
7483
8819
|
// src/panel.ts
|
|
7484
8820
|
import { homedir as homedir2 } from "node:os";
|
|
7485
|
-
import
|
|
8821
|
+
import path12 from "node:path";
|
|
7486
8822
|
|
|
7487
8823
|
// src/version.ts
|
|
7488
|
-
var CLI_VERSION = "1.
|
|
8824
|
+
var CLI_VERSION = "1.6.0";
|
|
7489
8825
|
|
|
7490
8826
|
// src/panel.ts
|
|
7491
8827
|
function welcomeSource(flags = {}) {
|
|
@@ -7509,7 +8845,7 @@ function welcomeSource(flags = {}) {
|
|
|
7509
8845
|
}
|
|
7510
8846
|
function tilde(dir) {
|
|
7511
8847
|
const home = homedir2();
|
|
7512
|
-
return dir === home || dir.startsWith(`${home}${
|
|
8848
|
+
return dir === home || dir.startsWith(`${home}${path12.sep}`) ? `~${dir.slice(home.length)}` : dir;
|
|
7513
8849
|
}
|
|
7514
8850
|
function hostOf(url) {
|
|
7515
8851
|
try {
|
|
@@ -7576,7 +8912,7 @@ function planTool(tool) {
|
|
|
7576
8912
|
}
|
|
7577
8913
|
function printScan(scan) {
|
|
7578
8914
|
out(
|
|
7579
|
-
`${symbol.pass}
|
|
8915
|
+
`${symbol.pass} Searched ${scan.filesScanned.toLocaleString("en-US")} file${scan.filesScanned === 1 ? "" : "s"}, read ${scan.filesParsed.toLocaleString("en-US")}` + (scan.truncated ? style.amber(" (ran out of time; findings may be incomplete)") : "") + style.grey(" \u2014 locally, nothing was uploaded")
|
|
7580
8916
|
);
|
|
7581
8917
|
if (scan.tools.length === 0) {
|
|
7582
8918
|
out(`${symbol.warn} No tools found, so there is nothing for Veefore to decide on yet.`);
|
|
@@ -7609,6 +8945,40 @@ function printScan(scan) {
|
|
|
7609
8945
|
`${symbol.warn} Your code reads ${service.length} credential${service.length === 1 ? "" : "s"}: ${service.map((s) => s.name).join(", ")} ${style.grey("(names only; values were not read)")}`
|
|
7610
8946
|
);
|
|
7611
8947
|
}
|
|
8948
|
+
printGaps(scan);
|
|
8949
|
+
}
|
|
8950
|
+
function printGaps(scan) {
|
|
8951
|
+
let discovery;
|
|
8952
|
+
try {
|
|
8953
|
+
discovery = discoverAgents(scan.root, scan);
|
|
8954
|
+
} catch {
|
|
8955
|
+
return;
|
|
8956
|
+
}
|
|
8957
|
+
if (discovery.agents.length > 1) {
|
|
8958
|
+
out();
|
|
8959
|
+
out(`${symbol.info} ${discovery.agents.length} agents found in this project:`);
|
|
8960
|
+
for (const agent of discovery.agents) {
|
|
8961
|
+
out(
|
|
8962
|
+
` ${style.bold(agent.name ?? agent.directory)} ${style.grey(`${agent.directory} \u2014 ${agent.tools.length} tool${agent.tools.length === 1 ? "" : "s"}`)}`
|
|
8963
|
+
);
|
|
8964
|
+
}
|
|
8965
|
+
out(style.grey(" Set each one up from its own directory, so its tools stay its own."));
|
|
8966
|
+
}
|
|
8967
|
+
if (discovery.unreadable.length === 0) return;
|
|
8968
|
+
const shown = discovery.unreadable.slice(0, 5);
|
|
8969
|
+
out();
|
|
8970
|
+
out(
|
|
8971
|
+
`${symbol.warn} ${discovery.unreadable.length} file${discovery.unreadable.length === 1 ? "" : "s"} look${discovery.unreadable.length === 1 ? "s" : ""} like agent code whose tools could not be read:`
|
|
8972
|
+
);
|
|
8973
|
+
for (const gap of shown) out(` ${style.grey(`${gap.file}:${gap.line}`)}`);
|
|
8974
|
+
if (discovery.unreadable.length > shown.length) {
|
|
8975
|
+
out(style.grey(` \u2026and ${discovery.unreadable.length - shown.length} more`));
|
|
8976
|
+
}
|
|
8977
|
+
out(
|
|
8978
|
+
style.grey(
|
|
8979
|
+
" Tools defined in a shape this scan does not know are not protected. Register them\n in the console by hand, or report the shape so it can be detected."
|
|
8980
|
+
)
|
|
8981
|
+
);
|
|
7612
8982
|
}
|
|
7613
8983
|
function printPlan2(plan, environment) {
|
|
7614
8984
|
out();
|
|
@@ -8570,7 +9940,7 @@ async function initCommand(args, flags) {
|
|
|
8570
9940
|
}
|
|
8571
9941
|
function previousApprovalMode(dir = process.cwd()) {
|
|
8572
9942
|
try {
|
|
8573
|
-
const text =
|
|
9943
|
+
const text = readFileSync10(path13.join(dir, ".env.local"), "utf8");
|
|
8574
9944
|
const value = /^\s*VEEFORE_APPROVAL_MODE\s*=\s*['"]?(\w+)/m.exec(text)?.[1]?.toLowerCase();
|
|
8575
9945
|
return value === "wait" || value === "throw" ? value : null;
|
|
8576
9946
|
} catch {
|