veefore 1.5.0 → 1.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/veefore.mjs +650 -147
- package/package.json +1 -1
package/dist/veefore.mjs
CHANGED
|
@@ -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,
|
|
@@ -4258,6 +4349,9 @@ function findDispatchGuards(sources) {
|
|
|
4258
4349
|
const dynamic = /(?<![\w$.])[A-Za-z_$]*[gG]uard\(\s*(?!['"`])[A-Za-z_$][\w$.]*(?:\[[^\]\n]{1,40}\])?\s*[,)]/;
|
|
4259
4350
|
return sources.filter(({ text }) => imports.test(text) && dynamic.test(stripDefinitions(text))).map(({ file }) => file);
|
|
4260
4351
|
}
|
|
4352
|
+
function escapeRegex(value) {
|
|
4353
|
+
return value.replace(/[.*+?^${}()|[\]\\/]/g, "\\$&");
|
|
4354
|
+
}
|
|
4261
4355
|
function stripDefinitions(text) {
|
|
4262
4356
|
return text.replace(/\b(?:function|def)\s+[A-Za-z_$]*[gG]uard\s*\(/g, "");
|
|
4263
4357
|
}
|
|
@@ -4267,6 +4361,11 @@ function toCamel(key) {
|
|
|
4267
4361
|
function findGuardedKeys(sources) {
|
|
4268
4362
|
const found = /* @__PURE__ */ new Map();
|
|
4269
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
|
+
}
|
|
4270
4369
|
for (const { file, text } of sources) {
|
|
4271
4370
|
for (const match of text.matchAll(pattern)) {
|
|
4272
4371
|
const key = match[1];
|
|
@@ -4275,6 +4374,24 @@ function findGuardedKeys(sources) {
|
|
|
4275
4374
|
}
|
|
4276
4375
|
return found;
|
|
4277
4376
|
}
|
|
4377
|
+
function dynamicCheckpointKeys(text, file) {
|
|
4378
|
+
const found = [];
|
|
4379
|
+
const calls = /(?<![\w$.])[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) 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
|
+
}
|
|
4278
4395
|
var PROVIDER_ENV = {
|
|
4279
4396
|
anthropic: "ANTHROPIC_API_KEY",
|
|
4280
4397
|
openai: "OPENAI_API_KEY"
|
|
@@ -5006,7 +5123,20 @@ var SKIP_DIRECTORIES = /* @__PURE__ */ new Set([
|
|
|
5006
5123
|
var SKIP_FILES = /\.(test|spec)\.[cm]?[jt]sx?$|^test_.*\.py$|_test\.py$|\.d\.ts$|\.min\.[cm]?js$/;
|
|
5007
5124
|
var DEFAULT_BUDGET_MS = 6e4;
|
|
5008
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
|
+
}
|
|
5009
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) {
|
|
5010
5140
|
const deadline2 = Date.now() + (options.budgetMs ?? DEFAULT_BUDGET_MS);
|
|
5011
5141
|
const fromGit = gitFiles(root, deadline2);
|
|
5012
5142
|
if (fromGit) {
|
|
@@ -5266,8 +5396,8 @@ function listSources(root = process.cwd()) {
|
|
|
5266
5396
|
const index = indexFiles(root);
|
|
5267
5397
|
return { files: index.files, truncated: index.truncated, method: index.method };
|
|
5268
5398
|
}
|
|
5269
|
-
function discoverAgents(root = process.cwd()) {
|
|
5270
|
-
const scan = scanProject(root);
|
|
5399
|
+
function discoverAgents(root = process.cwd(), reuse) {
|
|
5400
|
+
const scan = reuse ?? scanProject(root);
|
|
5271
5401
|
const index = indexFiles(root);
|
|
5272
5402
|
const byDirectory = /* @__PURE__ */ new Map();
|
|
5273
5403
|
for (const tool of scan.tools) {
|
|
@@ -5391,7 +5521,7 @@ function combineRisk(inferred, declared) {
|
|
|
5391
5521
|
function findJsTools(text) {
|
|
5392
5522
|
const hits = [];
|
|
5393
5523
|
const importsFrom = (pkg) => new RegExp(
|
|
5394
|
-
`from\\s+['"]${
|
|
5524
|
+
`from\\s+['"]${escapeRegex2(pkg)}(/[^'"]*)?['"]|require\\(\\s*['"]${escapeRegex2(pkg)}`
|
|
5395
5525
|
).test(text);
|
|
5396
5526
|
const vercel = importsFrom("ai");
|
|
5397
5527
|
const langchain = importsFrom("@langchain/core") || importsFrom("langchain");
|
|
@@ -5924,7 +6054,7 @@ function push(map, key, value) {
|
|
|
5924
6054
|
function unique(values) {
|
|
5925
6055
|
return [...new Set(values)];
|
|
5926
6056
|
}
|
|
5927
|
-
function
|
|
6057
|
+
function escapeRegex2(value) {
|
|
5928
6058
|
return value.replace(/[.*+?^${}()|[\]\\/]/g, "\\$&");
|
|
5929
6059
|
}
|
|
5930
6060
|
|
|
@@ -6528,6 +6658,185 @@ function guardDispatch(source, fn, typescript) {
|
|
|
6528
6658
|
return { after, at: target.start };
|
|
6529
6659
|
}
|
|
6530
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
|
+
}
|
|
6531
6840
|
function planCheckpoints(files, read, toolKeys) {
|
|
6532
6841
|
const plans = [];
|
|
6533
6842
|
const outstanding = new Set(toolKeys);
|
|
@@ -6541,24 +6850,41 @@ function planCheckpoints(files, read, toolKeys) {
|
|
|
6541
6850
|
(key) => source.includes(key) || source.includes(camelOf(key))
|
|
6542
6851
|
);
|
|
6543
6852
|
if (present.length === 0) continue;
|
|
6544
|
-
const
|
|
6853
|
+
const structure = readStructure(source);
|
|
6545
6854
|
const lineOf = lineLocator2(source);
|
|
6546
6855
|
const alias = chooseAliasFor(source, "checkpoint", "js");
|
|
6547
|
-
const
|
|
6548
|
-
const placed = [];
|
|
6856
|
+
const sites = /* @__PURE__ */ new Map();
|
|
6549
6857
|
for (const key of present) {
|
|
6550
|
-
const site = checkpointSite(source, key,
|
|
6858
|
+
const site = checkpointSite(source, key, structure);
|
|
6551
6859
|
if (!site) continue;
|
|
6552
|
-
const
|
|
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 || site.shared === true;
|
|
6870
|
+
const keyExpression = many && site.keyExpression ? site.keyExpression : quote(keys[0] ?? "");
|
|
6871
|
+
if (many && !site.keyExpression) continue;
|
|
6553
6872
|
const args = site.argsExpression ? `, ${site.argsExpression}` : "";
|
|
6554
6873
|
edits.push({
|
|
6555
|
-
start:
|
|
6556
|
-
end:
|
|
6874
|
+
start: at,
|
|
6875
|
+
end: at,
|
|
6557
6876
|
text: `
|
|
6558
|
-
${
|
|
6877
|
+
${blockIndent(source, at)}await ${alias}(${keyExpression}${args});`
|
|
6559
6878
|
});
|
|
6560
|
-
|
|
6561
|
-
|
|
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
|
+
}
|
|
6562
6888
|
}
|
|
6563
6889
|
if (edits.length === 0) continue;
|
|
6564
6890
|
const after = addJsSdkImport(applyEdits(source, edits), "checkpoint", alias);
|
|
@@ -6566,30 +6892,97 @@ ${indent}await ${alias}(${quote(key)}${args});`
|
|
|
6566
6892
|
}
|
|
6567
6893
|
return plans;
|
|
6568
6894
|
}
|
|
6569
|
-
function
|
|
6895
|
+
function planCheckpointRepairs(files, read, toolKeys) {
|
|
6896
|
+
const plans = [];
|
|
6897
|
+
const known = new Set(toolKeys);
|
|
6898
|
+
for (const file of files) {
|
|
6899
|
+
if (file.endsWith(".py") || !JS_EXTENSIONS.test(file)) continue;
|
|
6900
|
+
const source = read(file);
|
|
6901
|
+
if (source === null || !/[cC]heckpoint\(/.test(source)) continue;
|
|
6902
|
+
const structure = readStructure(source);
|
|
6903
|
+
const lineOf = lineLocator2(source);
|
|
6904
|
+
const edits = [];
|
|
6905
|
+
const placed = [];
|
|
6906
|
+
const calls = /(?<![\w$.])([A-Za-z_$]*[cC]heckpoint)\(\s*(['"`])([a-z][a-z0-9_]*)\2/g;
|
|
6907
|
+
for (const match of source.matchAll(calls)) {
|
|
6908
|
+
const key = match[3];
|
|
6909
|
+
if (!known.has(key)) continue;
|
|
6910
|
+
const at = match.index ?? 0;
|
|
6911
|
+
const group = enclosing(structure.parens, at).find(
|
|
6912
|
+
(candidate) => BRANCH_KEYWORDS.has(candidate.name)
|
|
6913
|
+
);
|
|
6914
|
+
const head = group ?? innermostBranchAbove(source, structure, at);
|
|
6915
|
+
if (!head) continue;
|
|
6916
|
+
const compared = comparedKeyExpression(source, head, key);
|
|
6917
|
+
if (!compared) continue;
|
|
6918
|
+
const served = branchKeys(source, head, compared);
|
|
6919
|
+
if (served.length < 2 || !served.includes(key)) continue;
|
|
6920
|
+
const keyStart = at + match[0].indexOf(match[2], match[1].length);
|
|
6921
|
+
edits.push({ start: keyStart, end: keyStart + (key.length + 2), text: compared });
|
|
6922
|
+
for (const served_key of served) {
|
|
6923
|
+
placed.push({ key: served_key, line: lineOf(at), shape: "branch", dynamic: true });
|
|
6924
|
+
}
|
|
6925
|
+
}
|
|
6926
|
+
if (edits.length === 0) continue;
|
|
6927
|
+
plans.push({ file, before: source, after: applyEdits(source, edits), placed });
|
|
6928
|
+
}
|
|
6929
|
+
return plans;
|
|
6930
|
+
}
|
|
6931
|
+
function innermostBranchAbove(source, structure, at) {
|
|
6932
|
+
const body = innermost(
|
|
6933
|
+
structure.blocks.filter((block) => !block.fn),
|
|
6934
|
+
at
|
|
6935
|
+
);
|
|
6936
|
+
if (!body) return null;
|
|
6937
|
+
return structure.parens.filter((group) => BRANCH_KEYWORDS.has(group.name) && group.close < body.open).sort((a, b) => b.close - a.close)[0] ?? null;
|
|
6938
|
+
}
|
|
6939
|
+
function comparedKeyExpression(source, head, key) {
|
|
6940
|
+
const text = source.slice(head.open, head.close);
|
|
6941
|
+
const pattern = new RegExp(
|
|
6942
|
+
`(?<![\\w$.])([A-Za-z_$][\\w$]*(?:\\s*(?:\\?\\.|\\.)\\s*[A-Za-z_$][\\w$]*)*)\\s*={2,3}\\s*['"\`]${escapeForRegex(key)}['"\`]`
|
|
6943
|
+
);
|
|
6944
|
+
const found = pattern.exec(text);
|
|
6945
|
+
return found ? found[1].replace(/\s+/g, "") : null;
|
|
6946
|
+
}
|
|
6947
|
+
function checkpointSite(source, key, structure) {
|
|
6570
6948
|
const quoted = `['"\`]${escapeForRegex(key)}['"\`]`;
|
|
6571
|
-
const usable = (at) =>
|
|
6949
|
+
const usable = (at) => awaitAllowed(structure, at) === true;
|
|
6950
|
+
const bodyAfter = (close) => {
|
|
6951
|
+
const brace = /^\s*\{/.exec(source.slice(close + 1, close + 200));
|
|
6952
|
+
return brace ? close + 1 + brace[0].length : null;
|
|
6953
|
+
};
|
|
6572
6954
|
for (const match of source.matchAll(new RegExp(`\\bcase\\s+${quoted}\\s*:`, "g"))) {
|
|
6573
6955
|
const colon = (match.index ?? 0) + match[0].length;
|
|
6574
6956
|
const braced = /^\s*\{/.exec(source.slice(colon));
|
|
6575
6957
|
const at = braced ? colon + braced[0].length : colon;
|
|
6576
6958
|
if (!usable(at)) continue;
|
|
6577
|
-
|
|
6578
|
-
}
|
|
6579
|
-
const comparison = new RegExp(
|
|
6580
|
-
`(?:if|else\\s+if)\\s*\\(([^()]{0,200}?)${quoted}([^()]{0,200}?)\\)\\s*\\{`,
|
|
6581
|
-
"g"
|
|
6582
|
-
);
|
|
6583
|
-
for (const match of source.matchAll(comparison)) {
|
|
6584
|
-
const left = match[1] ?? "";
|
|
6585
|
-
if (!/(?<![!<>])={2,3}\s*$/.test(left)) continue;
|
|
6586
|
-
const at = (match.index ?? 0) + match[0].length;
|
|
6587
|
-
if (!usable(at)) continue;
|
|
6588
|
-
const object = /([A-Za-z_$][\w$]*)\s*(?:\?\.|\.)\s*[A-Za-z_$][\w$]*\s*={2,3}\s*$/.exec(left);
|
|
6959
|
+
const discriminant = switchKey(source, match.index ?? 0);
|
|
6589
6960
|
return {
|
|
6590
6961
|
at,
|
|
6591
|
-
shape: "
|
|
6592
|
-
argsExpression:
|
|
6962
|
+
shape: "case",
|
|
6963
|
+
argsExpression: discriminant ? argumentsOf(source, discriminant.object) : null,
|
|
6964
|
+
keyExpression: discriminant?.expression ?? null
|
|
6965
|
+
};
|
|
6966
|
+
}
|
|
6967
|
+
for (const match of source.matchAll(new RegExp(quoted, "g"))) {
|
|
6968
|
+
const at = match.index ?? 0;
|
|
6969
|
+
if (!positiveComparison(source, at)) continue;
|
|
6970
|
+
const group = enclosing(structure.parens, at).find(
|
|
6971
|
+
(candidate) => BRANCH_KEYWORDS.has(candidate.name)
|
|
6972
|
+
);
|
|
6973
|
+
if (!group) continue;
|
|
6974
|
+
const insertAt = bodyAfter(group.close);
|
|
6975
|
+
if (insertAt === null || !usable(insertAt)) continue;
|
|
6976
|
+
const compared = comparedExpression(source, at);
|
|
6977
|
+
const loop = group.name === "for" ? loopVariable(source, group.open) : null;
|
|
6978
|
+
const subject = loop ?? compared?.object ?? null;
|
|
6979
|
+
const served = compared ? branchKeys(source, group, compared.expression) : [];
|
|
6980
|
+
return {
|
|
6981
|
+
at: insertAt,
|
|
6982
|
+
shape: group.name === "if" ? "branch" : "loop",
|
|
6983
|
+
argsExpression: subject ? argumentsOf(source, subject) : enclosingArguments(source, structure, insertAt),
|
|
6984
|
+
keyExpression: loop ? `${loop}.name` : compared?.expression ?? null,
|
|
6985
|
+
shared: served.length > 1
|
|
6593
6986
|
};
|
|
6594
6987
|
}
|
|
6595
6988
|
const declaration = new RegExp(
|
|
@@ -6599,18 +6992,23 @@ function checkpointSite(source, key, scopes) {
|
|
|
6599
6992
|
for (const match of source.matchAll(declaration)) {
|
|
6600
6993
|
const name = match[1];
|
|
6601
6994
|
const end = (match.index ?? 0) + match[0].length;
|
|
6602
|
-
const
|
|
6603
|
-
|
|
6995
|
+
const block = innermost(structure.blocks, end);
|
|
6996
|
+
const limit = block ? block.close : source.length;
|
|
6997
|
+
const head = new RegExp(`\\bif\\s*\\(\\s*${escapeForRegex(name)}\\b`).exec(
|
|
6998
|
+
source.slice(end, limit)
|
|
6604
6999
|
);
|
|
6605
7000
|
if (!head) continue;
|
|
6606
|
-
const parenOpen = end + head[0].
|
|
6607
|
-
const
|
|
6608
|
-
if (
|
|
6609
|
-
const
|
|
6610
|
-
if (!
|
|
6611
|
-
|
|
6612
|
-
|
|
6613
|
-
|
|
7001
|
+
const parenOpen = end + (head.index ?? 0) + head[0].indexOf("(");
|
|
7002
|
+
const group = structure.parens.find((candidate) => candidate.open === parenOpen);
|
|
7003
|
+
if (!group) continue;
|
|
7004
|
+
const insertAt = bodyAfter(group.close);
|
|
7005
|
+
if (insertAt === null || !usable(insertAt)) continue;
|
|
7006
|
+
return {
|
|
7007
|
+
at: insertAt,
|
|
7008
|
+
shape: "found",
|
|
7009
|
+
argsExpression: argumentsOf(source, name),
|
|
7010
|
+
keyExpression: `${name}.name`
|
|
7011
|
+
};
|
|
6614
7012
|
}
|
|
6615
7013
|
const handler = new RegExp(
|
|
6616
7014
|
`${quoted}\\s*:\\s*(?:async\\s*)?\\(([^)]{0,200})\\)\\s*(?::[^=>{]{0,120})?=>\\s*\\{`,
|
|
@@ -6619,28 +7017,68 @@ function checkpointSite(source, key, scopes) {
|
|
|
6619
7017
|
for (const match of source.matchAll(handler)) {
|
|
6620
7018
|
const at = (match.index ?? 0) + match[0].length;
|
|
6621
7019
|
if (!usable(at)) continue;
|
|
6622
|
-
return {
|
|
7020
|
+
return {
|
|
7021
|
+
at,
|
|
7022
|
+
shape: "handler",
|
|
7023
|
+
argsExpression: parameterExpression(match[1] ?? ""),
|
|
7024
|
+
keyExpression: null
|
|
7025
|
+
};
|
|
6623
7026
|
}
|
|
6624
|
-
const camel = camelOf(key);
|
|
6625
7027
|
const named = new RegExp(
|
|
6626
|
-
`\\basync\\s+function\\s+(?:${escapeForRegex(
|
|
7028
|
+
`\\basync\\s+function\\s+(?:${escapeForRegex(camelOf(key))}|${escapeForRegex(key)})\\s*\\(`,
|
|
6627
7029
|
"g"
|
|
6628
7030
|
);
|
|
6629
7031
|
for (const match of source.matchAll(named)) {
|
|
6630
7032
|
const parenOpen = (match.index ?? 0) + match[0].length - 1;
|
|
6631
|
-
const
|
|
6632
|
-
if (
|
|
6633
|
-
const
|
|
6634
|
-
if (
|
|
6635
|
-
if (!/^\s*(?::[^{;]*)?$/.test(source.slice(parenClose + 1, bodyOpen))) continue;
|
|
7033
|
+
const group = structure.parens.find((candidate) => candidate.open === parenOpen);
|
|
7034
|
+
if (!group) continue;
|
|
7035
|
+
const body = structure.blocks.filter((block) => block.fn && block.open > group.close).sort((a, b) => a.open - b.open)[0];
|
|
7036
|
+
if (!body) continue;
|
|
6636
7037
|
return {
|
|
6637
|
-
at:
|
|
7038
|
+
at: body.open + 1,
|
|
6638
7039
|
shape: "function",
|
|
6639
|
-
argsExpression: parameterExpression(source.slice(
|
|
7040
|
+
argsExpression: parameterExpression(source.slice(group.open + 1, group.close)),
|
|
7041
|
+
keyExpression: null
|
|
6640
7042
|
};
|
|
6641
7043
|
}
|
|
6642
7044
|
return null;
|
|
6643
7045
|
}
|
|
7046
|
+
function positiveComparison(source, at) {
|
|
7047
|
+
const before = source.slice(Math.max(0, at - 96), at);
|
|
7048
|
+
return /(?<![!<>])={2,3}\s*$/.test(before);
|
|
7049
|
+
}
|
|
7050
|
+
function comparedExpression(source, at) {
|
|
7051
|
+
const before = source.slice(Math.max(0, at - 160), at);
|
|
7052
|
+
const member = /([A-Za-z_$][\w$]*)\s*(?:\?\.|\.)\s*([A-Za-z_$][\w$]*)\s*={2,3}\s*$/.exec(before);
|
|
7053
|
+
if (member) return { object: member[1], expression: `${member[1]}.${member[2]}` };
|
|
7054
|
+
const bare = /(?<![\w$.])([A-Za-z_$][\w$]*)\s*={2,3}\s*$/.exec(before);
|
|
7055
|
+
if (bare) return { object: null, expression: bare[1] };
|
|
7056
|
+
return null;
|
|
7057
|
+
}
|
|
7058
|
+
function branchKeys(source, group, expression) {
|
|
7059
|
+
const head = source.slice(group.open, group.close);
|
|
7060
|
+
const [object, ...rest] = expression.split(".");
|
|
7061
|
+
if (object === void 0) return [];
|
|
7062
|
+
const access = rest.map((part) => `\\s*(?:\\?\\.|\\.)\\s*${escapeForRegex(part)}`).join("");
|
|
7063
|
+
const pattern = new RegExp(
|
|
7064
|
+
`(?<![\\w$.])${escapeForRegex(object)}${access}\\s*={2,3}\\s*['"\`]([a-z][a-z0-9_]*)['"\`]`,
|
|
7065
|
+
"g"
|
|
7066
|
+
);
|
|
7067
|
+
return [...new Set([...head.matchAll(pattern)].map((match) => match[1]))];
|
|
7068
|
+
}
|
|
7069
|
+
function switchKey(source, at) {
|
|
7070
|
+
const before = source.slice(Math.max(0, at - 4e3), at);
|
|
7071
|
+
const found = [
|
|
7072
|
+
...before.matchAll(/\bswitch\s*\(\s*([A-Za-z_$][\w$]*)\s*(?:\?\.|\.)\s*([A-Za-z_$][\w$]*)/g)
|
|
7073
|
+
];
|
|
7074
|
+
const last = found[found.length - 1];
|
|
7075
|
+
return last ? { object: last[1], expression: `${last[1]}.${last[2]}` } : null;
|
|
7076
|
+
}
|
|
7077
|
+
function loopVariable(source, parenOpen) {
|
|
7078
|
+
return /^\s*\(\s*(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s+of\b/.exec(
|
|
7079
|
+
source.slice(parenOpen, parenOpen + 120)
|
|
7080
|
+
)?.[1] ?? null;
|
|
7081
|
+
}
|
|
6644
7082
|
function parameterExpression(parameters) {
|
|
6645
7083
|
const names = parameters.split(",").map((part) => /^\s*(?:\.\.\.)?([A-Za-z_$][\w$]*)/.exec(part)?.[1]).filter((name) => name !== void 0);
|
|
6646
7084
|
const named = names.find((name) => CHECKPOINT_ARG_PROPERTIES.includes(name));
|
|
@@ -6650,14 +7088,6 @@ function parameterExpression(parameters) {
|
|
|
6650
7088
|
function camelOf(key) {
|
|
6651
7089
|
return key.replace(/_([a-z0-9])/g, (_match, char) => char.toUpperCase());
|
|
6652
7090
|
}
|
|
6653
|
-
function switchArguments(source, at) {
|
|
6654
|
-
const before = source.slice(Math.max(0, at - 4e3), at);
|
|
6655
|
-
const found = [
|
|
6656
|
-
...before.matchAll(/\bswitch\s*\(\s*([A-Za-z_$][\w$]*)\s*(?:\?\.|\.)\s*[A-Za-z_$][\w$]*/g)
|
|
6657
|
-
];
|
|
6658
|
-
const last = found[found.length - 1];
|
|
6659
|
-
return last ? argumentsOf(source, last[1]) : null;
|
|
6660
|
-
}
|
|
6661
7091
|
function argumentsOf(source, object) {
|
|
6662
7092
|
for (const property of CHECKPOINT_ARG_PROPERTIES) {
|
|
6663
7093
|
const pattern = new RegExp(`\\b${escapeForRegex(object)}\\s*(?:\\?\\.|\\.)\\s*${property}\\b`);
|
|
@@ -6675,63 +7105,6 @@ function blockIndent(source, at) {
|
|
|
6675
7105
|
const lineStart = source.lastIndexOf("\n", at) + 1;
|
|
6676
7106
|
return `${/^[ \t]*/.exec(source.slice(lineStart, at))?.[0] ?? ""} `;
|
|
6677
7107
|
}
|
|
6678
|
-
function functionScopes(source) {
|
|
6679
|
-
const scopes = [];
|
|
6680
|
-
const add = (isAsync, parenOpen, arrow) => {
|
|
6681
|
-
const parenClose = matchBracket(source, parenOpen);
|
|
6682
|
-
if (parenClose < 0) return;
|
|
6683
|
-
const bodyOpen = source.indexOf("{", parenClose);
|
|
6684
|
-
if (bodyOpen < 0) return;
|
|
6685
|
-
const between = source.slice(parenClose + 1, bodyOpen);
|
|
6686
|
-
if (arrow !== /=>/.test(between)) return;
|
|
6687
|
-
if (!/^\s*(?::[^{;=]*)?(?:=>\s*)?$/.test(between)) return;
|
|
6688
|
-
const bodyClose = matchBracket(source, bodyOpen);
|
|
6689
|
-
if (bodyClose < 0) return;
|
|
6690
|
-
scopes.push({ bodyOpen, bodyClose, async: isAsync });
|
|
6691
|
-
};
|
|
6692
|
-
for (const match of source.matchAll(/\b(async\s+)?function\s*\*?\s*[A-Za-z_$][\w$]*\s*\(/g)) {
|
|
6693
|
-
add(match[1] !== void 0, (match.index ?? 0) + match[0].length - 1, false);
|
|
6694
|
-
}
|
|
6695
|
-
for (const match of source.matchAll(/(\basync\s*)?\(/g)) {
|
|
6696
|
-
add(match[1] !== void 0, (match.index ?? 0) + match[0].length - 1, true);
|
|
6697
|
-
}
|
|
6698
|
-
const method = /^[ \t]*((?:(?:public|private|protected|static|override|async|get|set)\s+)*)\*?\s*([A-Za-z_$][\w$]*)\s*\(/gm;
|
|
6699
|
-
for (const match of source.matchAll(method)) {
|
|
6700
|
-
if (CONTROL_KEYWORDS.has(match[2])) continue;
|
|
6701
|
-
add(/\basync\b/.test(match[1] ?? ""), (match.index ?? 0) + match[0].length - 1, false);
|
|
6702
|
-
}
|
|
6703
|
-
for (const match of source.matchAll(/\b(async\s+)?([A-Za-z_$][\w$]*)\s*=>\s*\{/g)) {
|
|
6704
|
-
const bodyOpen = (match.index ?? 0) + match[0].length - 1;
|
|
6705
|
-
const bodyClose = matchBracket(source, bodyOpen);
|
|
6706
|
-
if (bodyClose > 0) scopes.push({ bodyOpen, bodyClose, async: match[1] !== void 0 });
|
|
6707
|
-
}
|
|
6708
|
-
return scopes;
|
|
6709
|
-
}
|
|
6710
|
-
var CONTROL_KEYWORDS = /* @__PURE__ */ new Set([
|
|
6711
|
-
"if",
|
|
6712
|
-
"else",
|
|
6713
|
-
"for",
|
|
6714
|
-
"while",
|
|
6715
|
-
"do",
|
|
6716
|
-
"switch",
|
|
6717
|
-
"catch",
|
|
6718
|
-
"with",
|
|
6719
|
-
"return",
|
|
6720
|
-
"typeof",
|
|
6721
|
-
"new",
|
|
6722
|
-
"await",
|
|
6723
|
-
"function",
|
|
6724
|
-
"throw",
|
|
6725
|
-
"case"
|
|
6726
|
-
]);
|
|
6727
|
-
function enclosingAsync(scopes, at) {
|
|
6728
|
-
let best = null;
|
|
6729
|
-
for (const scope of scopes) {
|
|
6730
|
-
if (at <= scope.bodyOpen || at > scope.bodyClose) continue;
|
|
6731
|
-
if (!best || scope.bodyOpen > best.bodyOpen) best = scope;
|
|
6732
|
-
}
|
|
6733
|
-
return best ? best.async : null;
|
|
6734
|
-
}
|
|
6735
7108
|
function unguardJs(source) {
|
|
6736
7109
|
const checkpoints = uncheckpointJs(source);
|
|
6737
7110
|
const imported = jsGuardImport(checkpoints.after);
|
|
@@ -6788,9 +7161,10 @@ function uncheckpointJs(source) {
|
|
|
6788
7161
|
const lineOf = lineLocator2(source);
|
|
6789
7162
|
const leftovers = [];
|
|
6790
7163
|
let removed = 0;
|
|
7164
|
+
const key = `(?:(['"\`])[a-z][a-z0-9_]*\\1|[A-Za-z_$][\\w$]*(?:\\s*(?:\\?\\.|\\.)\\s*[A-Za-z_$][\\w$]*)+)`;
|
|
6791
7165
|
let after = source.replace(
|
|
6792
7166
|
new RegExp(
|
|
6793
|
-
`^[ \\t]*(?:await[ \\t]+)?${escapeForRegex(alias)}\\(\\s
|
|
7167
|
+
`^[ \\t]*(?:await[ \\t]+)?${escapeForRegex(alias)}\\(\\s*${key}[^\\n]*\\)[ \\t]*;?[ \\t]*\\r?\\n`,
|
|
6794
7168
|
"gm"
|
|
6795
7169
|
),
|
|
6796
7170
|
() => {
|
|
@@ -6812,8 +7186,28 @@ function uncheckpointJs(source) {
|
|
|
6812
7186
|
function unguardPython(source) {
|
|
6813
7187
|
const imported = /^from[ \t]+veefore[ \t]+import[ \t]+([^\n]+)$/m.exec(source);
|
|
6814
7188
|
if (!imported) return { after: source, removed: 0, leftovers: [] };
|
|
6815
|
-
const
|
|
6816
|
-
|
|
7189
|
+
const names = (imported[1] ?? "").replace(/[()]/g, "").split(",");
|
|
7190
|
+
const spec = names.map((part) => /^guard(?:\s+as\s+(\w+))?$/.exec(part.trim())).find((found) => found !== null);
|
|
7191
|
+
const gate = names.map((part) => /^checkpoint(?:\s+as\s+(\w+))?$/.exec(part.trim())).find((found) => found !== null);
|
|
7192
|
+
if (!spec && !gate) return { after: source, removed: 0, leftovers: [] };
|
|
7193
|
+
if (!spec) {
|
|
7194
|
+
const alias2 = gate?.[1] ?? "checkpoint";
|
|
7195
|
+
let removed2 = 0;
|
|
7196
|
+
let after2 = source.replace(
|
|
7197
|
+
new RegExp(
|
|
7198
|
+
`^[ \\t]*${escapeForRegex(alias2)}\\(\\s*(['"])[a-z][a-z0-9_]*\\1[^\\n]*\\)[ \\t]*\\r?\\n`,
|
|
7199
|
+
"gm"
|
|
7200
|
+
),
|
|
7201
|
+
() => {
|
|
7202
|
+
removed2 += 1;
|
|
7203
|
+
return "";
|
|
7204
|
+
}
|
|
7205
|
+
);
|
|
7206
|
+
if (!new RegExp(`(?<![\\w.])${escapeForRegex(alias2)}\\s*\\(`).test(after2)) {
|
|
7207
|
+
after2 = removePythonImport(after2, alias2);
|
|
7208
|
+
}
|
|
7209
|
+
return { after: after2, removed: removed2, leftovers: [] };
|
|
7210
|
+
}
|
|
6817
7211
|
const alias = spec[1] ?? "guard";
|
|
6818
7212
|
const lineOf = lineLocator2(source);
|
|
6819
7213
|
let removed = 0;
|
|
@@ -6852,18 +7246,20 @@ function unguardPython(source) {
|
|
|
6852
7246
|
after = applyEdits(after, edits);
|
|
6853
7247
|
removed += edits.length;
|
|
6854
7248
|
const stillUsed = new RegExp(`(?<![\\w.])@?${escapeForRegex(alias)}\\s*\\(`).test(after);
|
|
6855
|
-
if (!stillUsed)
|
|
6856
|
-
after = after.replace(
|
|
6857
|
-
/^from[ \t]+veefore[ \t]+import[ \t]+([^\n]+)\r?\n?/m,
|
|
6858
|
-
(line, names) => {
|
|
6859
|
-
const rest = names.replace(/[()]/g, "").split(",").map((part) => part.trim()).filter((part) => part && !/^guard(?:\s+as\s+\w+)?$/.test(part));
|
|
6860
|
-
return rest.length === 0 ? "" : `from veefore import ${rest.join(", ")}
|
|
6861
|
-
`;
|
|
6862
|
-
}
|
|
6863
|
-
);
|
|
6864
|
-
}
|
|
7249
|
+
if (!stillUsed) after = removePythonImport(after, "guard");
|
|
6865
7250
|
return { after, removed, leftovers };
|
|
6866
7251
|
}
|
|
7252
|
+
function removePythonImport(source, name) {
|
|
7253
|
+
const target = new RegExp(`^${escapeForRegex(name)}(?:\\s+as\\s+\\w+)?$`);
|
|
7254
|
+
return source.replace(
|
|
7255
|
+
/^from[ \t]+veefore[ \t]+import[ \t]+([^\n]+)\r?\n?/m,
|
|
7256
|
+
(_line, names) => {
|
|
7257
|
+
const rest = names.replace(/[()]/g, "").split(",").map((part) => part.trim()).filter((part) => part && !target.test(part));
|
|
7258
|
+
return rest.length === 0 ? "" : `from veefore import ${rest.join(", ")}
|
|
7259
|
+
`;
|
|
7260
|
+
}
|
|
7261
|
+
);
|
|
7262
|
+
}
|
|
6867
7263
|
function arrowExpressionBody(fn) {
|
|
6868
7264
|
let cursor = 0;
|
|
6869
7265
|
if (fn.startsWith("(")) {
|
|
@@ -7107,7 +7503,6 @@ async function integrate(root, toolKeys, options) {
|
|
|
7107
7503
|
const candidates = shortlist(root, index, anchors);
|
|
7108
7504
|
const sourceFiles = candidates.files;
|
|
7109
7505
|
const truncated = index.truncated || candidates.truncated;
|
|
7110
|
-
const plan = planRewrite(root, sourceFiles, toolKeys);
|
|
7111
7506
|
const interactive = options.interactive ?? (process.stdin.isTTY === true && process.stdout.isTTY === true && !options.yes);
|
|
7112
7507
|
if (truncated && !options.quiet) {
|
|
7113
7508
|
out(
|
|
@@ -7118,13 +7513,39 @@ async function integrate(root, toolKeys, options) {
|
|
|
7118
7513
|
const dispatchGuardFiles = findDispatchGuards(sources);
|
|
7119
7514
|
const dispatchGuarded = dispatchGuardFiles.length > 0;
|
|
7120
7515
|
const alreadyGuarded = guardedToolKeys(sources, toolKeys);
|
|
7121
|
-
|
|
7122
|
-
for (const
|
|
7123
|
-
|
|
7124
|
-
change.status
|
|
7125
|
-
|
|
7516
|
+
const reconcile = (result) => {
|
|
7517
|
+
for (const file of result.files) {
|
|
7518
|
+
for (const change of file.changes) {
|
|
7519
|
+
if (change.status === "manual" && alreadyGuarded.has(change.key)) {
|
|
7520
|
+
change.status = "already";
|
|
7521
|
+
delete change.reason;
|
|
7522
|
+
}
|
|
7126
7523
|
}
|
|
7127
7524
|
}
|
|
7525
|
+
return result;
|
|
7526
|
+
};
|
|
7527
|
+
let plan = reconcile(planRewrite(root, sourceFiles, toolKeys));
|
|
7528
|
+
const changeable = [
|
|
7529
|
+
...new Set(
|
|
7530
|
+
plan.files.flatMap(
|
|
7531
|
+
(file) => file.changes.filter((change) => change.status !== "already").map((change) => change.key)
|
|
7532
|
+
)
|
|
7533
|
+
)
|
|
7534
|
+
];
|
|
7535
|
+
const selected = await chooseTools(changeable, plan, {
|
|
7536
|
+
requested: options.tools,
|
|
7537
|
+
interactive,
|
|
7538
|
+
quiet: options.quiet === true
|
|
7539
|
+
});
|
|
7540
|
+
const skipped = changeable.filter((key) => !selected.includes(key));
|
|
7541
|
+
if (skipped.length > 0) {
|
|
7542
|
+
plan = reconcile(
|
|
7543
|
+
planRewrite(
|
|
7544
|
+
root,
|
|
7545
|
+
sourceFiles,
|
|
7546
|
+
toolKeys.filter((key) => !skipped.includes(key))
|
|
7547
|
+
)
|
|
7548
|
+
);
|
|
7128
7549
|
}
|
|
7129
7550
|
let manual = plan.files.flatMap(
|
|
7130
7551
|
(file) => file.changes.filter((change) => change.status === "manual").map((change) => ({
|
|
@@ -7157,6 +7578,15 @@ async function integrate(root, toolKeys, options) {
|
|
|
7157
7578
|
dispatch = [];
|
|
7158
7579
|
}
|
|
7159
7580
|
}
|
|
7581
|
+
const repaired = applyCheckpoints(
|
|
7582
|
+
plan,
|
|
7583
|
+
planCheckpointRepairs(sourceFiles, (file) => currentText(root, plan, file), toolKeys)
|
|
7584
|
+
);
|
|
7585
|
+
if (repaired.size > 0 && !options.quiet) {
|
|
7586
|
+
out(
|
|
7587
|
+
`${symbol.warn} Corrected a guard that named one tool in a block that runs several: ${style.bold([...repaired].join(", "))} ${style.grey("(it now carries the key your code read)")}`
|
|
7588
|
+
);
|
|
7589
|
+
}
|
|
7160
7590
|
if (dispatch.length > 0 && checkpointUsable(root, options.quiet === true)) {
|
|
7161
7591
|
const placed = applyCheckpoints(
|
|
7162
7592
|
plan,
|
|
@@ -7270,7 +7700,15 @@ async function integrate(root, toolKeys, options) {
|
|
|
7270
7700
|
reportSdkReadiness(root, [.../* @__PURE__ */ new Set([...guardedFiles, ...dispatchGuardFiles])]);
|
|
7271
7701
|
}
|
|
7272
7702
|
const unguarded2 = reportProtection(root, sourceFiles, toolKeys, quiet);
|
|
7273
|
-
return {
|
|
7703
|
+
return {
|
|
7704
|
+
status: "nothing-to-do",
|
|
7705
|
+
plan,
|
|
7706
|
+
checks: [],
|
|
7707
|
+
backupId: null,
|
|
7708
|
+
manual,
|
|
7709
|
+
unguarded: unguarded2,
|
|
7710
|
+
skipped
|
|
7711
|
+
};
|
|
7274
7712
|
}
|
|
7275
7713
|
if (options.dryRun) {
|
|
7276
7714
|
out(style.grey("Dry run: nothing was written."));
|
|
@@ -7280,7 +7718,8 @@ async function integrate(root, toolKeys, options) {
|
|
|
7280
7718
|
checks: [],
|
|
7281
7719
|
backupId: null,
|
|
7282
7720
|
manual,
|
|
7283
|
-
unguarded: reportProtection(root, sourceFiles, toolKeys, true)
|
|
7721
|
+
unguarded: reportProtection(root, sourceFiles, toolKeys, true),
|
|
7722
|
+
skipped
|
|
7284
7723
|
};
|
|
7285
7724
|
}
|
|
7286
7725
|
if (!options.quiet) {
|
|
@@ -7301,7 +7740,8 @@ async function integrate(root, toolKeys, options) {
|
|
|
7301
7740
|
checks: [],
|
|
7302
7741
|
backupId: null,
|
|
7303
7742
|
manual,
|
|
7304
|
-
unguarded: reportProtection(root, sourceFiles, toolKeys, true)
|
|
7743
|
+
unguarded: reportProtection(root, sourceFiles, toolKeys, true),
|
|
7744
|
+
skipped
|
|
7305
7745
|
};
|
|
7306
7746
|
}
|
|
7307
7747
|
const jsReady = languages.has("js") && sdkInstalled(root, "js");
|
|
@@ -7318,12 +7758,14 @@ async function integrate(root, toolKeys, options) {
|
|
|
7318
7758
|
checks: [],
|
|
7319
7759
|
backupId: null,
|
|
7320
7760
|
manual,
|
|
7321
|
-
unguarded: reportProtection(root, sourceFiles, toolKeys, true)
|
|
7761
|
+
unguarded: reportProtection(root, sourceFiles, toolKeys, true),
|
|
7762
|
+
skipped
|
|
7322
7763
|
};
|
|
7323
7764
|
}
|
|
7324
7765
|
}
|
|
7325
7766
|
const backup = createBackup(root, toWrite);
|
|
7326
7767
|
for (const file of toWrite) writeFilePreservingMode(path10.join(root, file.file), file.after);
|
|
7768
|
+
forgetIndex(root);
|
|
7327
7769
|
out(
|
|
7328
7770
|
`${symbol.pass} Updated ${toWrite.length} file${toWrite.length === 1 ? "" : "s"} ${style.grey(`(backup ${backup.id})`)}`
|
|
7329
7771
|
);
|
|
@@ -7385,7 +7827,8 @@ async function integrate(root, toolKeys, options) {
|
|
|
7385
7827
|
checks,
|
|
7386
7828
|
backupId: null,
|
|
7387
7829
|
manual,
|
|
7388
|
-
unguarded: reportProtection(root, sourceFiles, toolKeys, true)
|
|
7830
|
+
unguarded: reportProtection(root, sourceFiles, toolKeys, true),
|
|
7831
|
+
skipped
|
|
7389
7832
|
};
|
|
7390
7833
|
}
|
|
7391
7834
|
for (const language of languages) {
|
|
@@ -7395,9 +7838,14 @@ async function integrate(root, toolKeys, options) {
|
|
|
7395
7838
|
);
|
|
7396
7839
|
}
|
|
7397
7840
|
}
|
|
7841
|
+
if (skipped.length > 0 && !options.quiet) {
|
|
7842
|
+
out(
|
|
7843
|
+
`${symbol.info} Left out of this run, as you chose: ${style.bold(skipped.join(", "))} ${style.grey("(no file was changed for these)")}`
|
|
7844
|
+
);
|
|
7845
|
+
}
|
|
7398
7846
|
const unguarded = reportProtection(root, sourceFiles, toolKeys, options.quiet === true);
|
|
7399
7847
|
out(style.grey(" Undo at any time with `veefore undo`."));
|
|
7400
|
-
return { status: "applied", plan, checks, backupId: backup.id, manual, unguarded };
|
|
7848
|
+
return { status: "applied", plan, checks, backupId: backup.id, manual, unguarded, skipped };
|
|
7401
7849
|
}
|
|
7402
7850
|
function reportProtection(root, files, toolKeys, quiet) {
|
|
7403
7851
|
const guarded = guardedToolKeys(readSources(root, files), toolKeys);
|
|
@@ -7560,6 +8008,43 @@ function stillManual(items, plan) {
|
|
|
7560
8008
|
return true;
|
|
7561
8009
|
});
|
|
7562
8010
|
}
|
|
8011
|
+
async function chooseTools(changeable, plan, options) {
|
|
8012
|
+
if (options.requested) {
|
|
8013
|
+
const asked = new Set(options.requested);
|
|
8014
|
+
const unknown = options.requested.filter((key) => !changeable.includes(key));
|
|
8015
|
+
if (unknown.length > 0 && !options.quiet) {
|
|
8016
|
+
out(
|
|
8017
|
+
`${symbol.warn} Nothing to change for: ${unknown.join(", ")} ${style.grey("(already protected, or not found in the code)")}`
|
|
8018
|
+
);
|
|
8019
|
+
}
|
|
8020
|
+
return changeable.filter((key) => asked.has(key));
|
|
8021
|
+
}
|
|
8022
|
+
if (!options.interactive || changeable.length < 2) return changeable;
|
|
8023
|
+
const where = /* @__PURE__ */ new Map();
|
|
8024
|
+
for (const file of plan.files) {
|
|
8025
|
+
for (const change of file.changes) {
|
|
8026
|
+
if (change.status === "already" || where.has(change.key)) continue;
|
|
8027
|
+
where.set(change.key, { line: change.line, file: file.file, status: change.status });
|
|
8028
|
+
}
|
|
8029
|
+
}
|
|
8030
|
+
try {
|
|
8031
|
+
return await selectMany(
|
|
8032
|
+
"Which tools should Veefore decide for?",
|
|
8033
|
+
changeable.map((key) => {
|
|
8034
|
+
const at = where.get(key);
|
|
8035
|
+
return {
|
|
8036
|
+
label: key,
|
|
8037
|
+
value: key,
|
|
8038
|
+
hint: at ? `${at.status === "wrapped" ? "guard here" : "needs a change"} \xB7 ${at.file}:${at.line}` : ""
|
|
8039
|
+
};
|
|
8040
|
+
}),
|
|
8041
|
+
{ help: "All of them unless you say otherwise. Only what is ticked gets changed." }
|
|
8042
|
+
);
|
|
8043
|
+
} catch (error) {
|
|
8044
|
+
if (error instanceof MenuCancelled) return [];
|
|
8045
|
+
throw error;
|
|
8046
|
+
}
|
|
8047
|
+
}
|
|
7563
8048
|
function checkpointUsable(root, quiet) {
|
|
7564
8049
|
const sdks = installedSdks(root).filter((sdk) => sdk.language === "js");
|
|
7565
8050
|
const stale = sdks.filter((sdk) => !sdk.hasCheckpoint);
|
|
@@ -7632,7 +8117,13 @@ function guardedToolKeys(sources, toolKeys) {
|
|
|
7632
8117
|
return new Map([...guarded].filter(([key]) => toolKeys.includes(key)));
|
|
7633
8118
|
}
|
|
7634
8119
|
function checkIntegration(root, toolKeys) {
|
|
7635
|
-
const
|
|
8120
|
+
const index = indexFiles(root);
|
|
8121
|
+
const candidates = shortlist(root, index, [
|
|
8122
|
+
...toolKeyAnchors(toolKeys),
|
|
8123
|
+
...TOOL_ANCHORS,
|
|
8124
|
+
...GUARD_ANCHORS
|
|
8125
|
+
]);
|
|
8126
|
+
const guarded = guardedToolKeys(readSources(root, candidates.files), toolKeys);
|
|
7636
8127
|
heading("Protection check");
|
|
7637
8128
|
for (const key of toolKeys) {
|
|
7638
8129
|
const file = guarded.get(key);
|
|
@@ -7934,10 +8425,20 @@ async function integrateCommand(args, flags) {
|
|
|
7934
8425
|
let assist;
|
|
7935
8426
|
let dryRun = false;
|
|
7936
8427
|
let check = false;
|
|
8428
|
+
let tools;
|
|
7937
8429
|
for (const arg of args) {
|
|
7938
8430
|
if (arg === "--dry-run") dryRun = true;
|
|
7939
8431
|
else if (arg === "--check") check = true;
|
|
7940
|
-
else if (arg
|
|
8432
|
+
else if (arg === "--tools=all") tools = void 0;
|
|
8433
|
+
else if (arg.startsWith("--tools=")) {
|
|
8434
|
+
tools = arg.slice("--tools=".length).split(",").map((key) => key.trim()).filter((key) => key !== "");
|
|
8435
|
+
if (tools.length === 0) {
|
|
8436
|
+
throw new CliError(
|
|
8437
|
+
"`--tools` takes a comma-separated list of tool keys, or `all`.",
|
|
8438
|
+
"For example: --tools=refund_order,adjust_stock"
|
|
8439
|
+
);
|
|
8440
|
+
}
|
|
8441
|
+
} else if (arg.startsWith("--assist=")) {
|
|
7941
8442
|
const value = ASSIST_VALUES[arg.slice("--assist=".length)];
|
|
7942
8443
|
if (!value) {
|
|
7943
8444
|
throw new CliError(
|
|
@@ -7980,7 +8481,8 @@ async function integrateCommand(args, flags) {
|
|
|
7980
8481
|
dryRun,
|
|
7981
8482
|
quiet: flags.json,
|
|
7982
8483
|
assist,
|
|
7983
|
-
api
|
|
8484
|
+
api,
|
|
8485
|
+
...tools ? { tools } : {}
|
|
7984
8486
|
});
|
|
7985
8487
|
if (flags.json) {
|
|
7986
8488
|
out(
|
|
@@ -7992,6 +8494,7 @@ async function integrateCommand(args, flags) {
|
|
|
7992
8494
|
not_found: outcome.plan.notFound,
|
|
7993
8495
|
manual: outcome.manual,
|
|
7994
8496
|
unguarded: outcome.unguarded,
|
|
8497
|
+
skipped: outcome.skipped,
|
|
7995
8498
|
checks: outcome.checks
|
|
7996
8499
|
},
|
|
7997
8500
|
null,
|
|
@@ -8394,7 +8897,7 @@ import { homedir as homedir2 } from "node:os";
|
|
|
8394
8897
|
import path12 from "node:path";
|
|
8395
8898
|
|
|
8396
8899
|
// src/version.ts
|
|
8397
|
-
var CLI_VERSION = "1.
|
|
8900
|
+
var CLI_VERSION = "1.6.1";
|
|
8398
8901
|
|
|
8399
8902
|
// src/panel.ts
|
|
8400
8903
|
function welcomeSource(flags = {}) {
|
|
@@ -8518,12 +9021,12 @@ function printScan(scan) {
|
|
|
8518
9021
|
`${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)")}`
|
|
8519
9022
|
);
|
|
8520
9023
|
}
|
|
8521
|
-
printGaps(scan
|
|
9024
|
+
printGaps(scan);
|
|
8522
9025
|
}
|
|
8523
|
-
function printGaps(
|
|
9026
|
+
function printGaps(scan) {
|
|
8524
9027
|
let discovery;
|
|
8525
9028
|
try {
|
|
8526
|
-
discovery = discoverAgents(root);
|
|
9029
|
+
discovery = discoverAgents(scan.root, scan);
|
|
8527
9030
|
} catch {
|
|
8528
9031
|
return;
|
|
8529
9032
|
}
|