veefore 1.5.0 → 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.
Files changed (2) hide show
  1. package/dist/veefore.mjs +574 -147
  2. 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 = /(?:\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
+ }
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+['"]${escapeRegex(pkg)}(/[^'"]*)?['"]|require\\(\\s*['"]${escapeRegex(pkg)}`
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 escapeRegex(value) {
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 scopes = functionScopes(source);
6853
+ const structure = readStructure(source);
6545
6854
  const lineOf = lineLocator2(source);
6546
6855
  const alias = chooseAliasFor(source, "checkpoint", "js");
6547
- const edits = [];
6548
- const placed = [];
6856
+ const sites = /* @__PURE__ */ new Map();
6549
6857
  for (const key of present) {
6550
- const site = checkpointSite(source, key, scopes);
6858
+ const site = checkpointSite(source, key, structure);
6551
6859
  if (!site) continue;
6552
- const indent = blockIndent(source, site.at);
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;
6553
6872
  const args = site.argsExpression ? `, ${site.argsExpression}` : "";
6554
6873
  edits.push({
6555
- start: site.at,
6556
- end: site.at,
6874
+ start: at,
6875
+ end: at,
6557
6876
  text: `
6558
- ${indent}await ${alias}(${quote(key)}${args});`
6877
+ ${blockIndent(source, at)}await ${alias}(${keyExpression}${args});`
6559
6878
  });
6560
- placed.push({ key, line: lineOf(site.at), shape: site.shape });
6561
- outstanding.delete(key);
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,43 @@ ${indent}await ${alias}(${quote(key)}${args});`
6566
6892
  }
6567
6893
  return plans;
6568
6894
  }
6569
- function checkpointSite(source, key, scopes) {
6895
+ function checkpointSite(source, key, structure) {
6570
6896
  const quoted = `['"\`]${escapeForRegex(key)}['"\`]`;
6571
- const usable = (at) => enclosingAsync(scopes, at) === true;
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
+ };
6572
6902
  for (const match of source.matchAll(new RegExp(`\\bcase\\s+${quoted}\\s*:`, "g"))) {
6573
6903
  const colon = (match.index ?? 0) + match[0].length;
6574
6904
  const braced = /^\s*\{/.exec(source.slice(colon));
6575
6905
  const at = braced ? colon + braced[0].length : colon;
6576
6906
  if (!usable(at)) continue;
6577
- return { at, shape: "case", argsExpression: switchArguments(source, match.index ?? 0) };
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);
6907
+ const discriminant = switchKey(source, match.index ?? 0);
6589
6908
  return {
6590
6909
  at,
6591
- shape: "branch",
6592
- argsExpression: object ? argumentsOf(source, object[1]) : null
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
6593
6932
  };
6594
6933
  }
6595
6934
  const declaration = new RegExp(
@@ -6599,18 +6938,23 @@ function checkpointSite(source, key, scopes) {
6599
6938
  for (const match of source.matchAll(declaration)) {
6600
6939
  const name = match[1];
6601
6940
  const end = (match.index ?? 0) + match[0].length;
6602
- const head = new RegExp(`^\\s*if\\s*\\(\\s*${escapeForRegex(name)}\\b`).exec(
6603
- source.slice(end, end + 200)
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)
6604
6945
  );
6605
6946
  if (!head) continue;
6606
- const parenOpen = end + head[0].lastIndexOf("(", head[0].length - name.length);
6607
- const parenClose = matchBracket(source, parenOpen);
6608
- if (parenClose < 0) continue;
6609
- const brace = /^\s*\{/.exec(source.slice(parenClose + 1));
6610
- if (!brace) continue;
6611
- const at = parenClose + 1 + brace[0].length;
6612
- if (!usable(at)) continue;
6613
- return { at, shape: "found", argsExpression: argumentsOf(source, name) };
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
+ };
6614
6958
  }
6615
6959
  const handler = new RegExp(
6616
6960
  `${quoted}\\s*:\\s*(?:async\\s*)?\\(([^)]{0,200})\\)\\s*(?::[^=>{]{0,120})?=>\\s*\\{`,
@@ -6619,28 +6963,55 @@ function checkpointSite(source, key, scopes) {
6619
6963
  for (const match of source.matchAll(handler)) {
6620
6964
  const at = (match.index ?? 0) + match[0].length;
6621
6965
  if (!usable(at)) continue;
6622
- return { at, shape: "handler", argsExpression: parameterExpression(match[1] ?? "") };
6966
+ return {
6967
+ at,
6968
+ shape: "handler",
6969
+ argsExpression: parameterExpression(match[1] ?? ""),
6970
+ keyExpression: null
6971
+ };
6623
6972
  }
6624
- const camel = camelOf(key);
6625
6973
  const named = new RegExp(
6626
- `\\basync\\s+function\\s+(?:${escapeForRegex(camel)}|${escapeForRegex(key)})\\s*\\(`,
6974
+ `\\basync\\s+function\\s+(?:${escapeForRegex(camelOf(key))}|${escapeForRegex(key)})\\s*\\(`,
6627
6975
  "g"
6628
6976
  );
6629
6977
  for (const match of source.matchAll(named)) {
6630
6978
  const parenOpen = (match.index ?? 0) + match[0].length - 1;
6631
- const parenClose = matchBracket(source, parenOpen);
6632
- if (parenClose < 0) continue;
6633
- const bodyOpen = source.indexOf("{", parenClose);
6634
- if (bodyOpen < 0) continue;
6635
- if (!/^\s*(?::[^{;]*)?$/.test(source.slice(parenClose + 1, bodyOpen))) continue;
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;
6636
6983
  return {
6637
- at: bodyOpen + 1,
6984
+ at: body.open + 1,
6638
6985
  shape: "function",
6639
- argsExpression: parameterExpression(source.slice(parenOpen + 1, parenClose))
6986
+ argsExpression: parameterExpression(source.slice(group.open + 1, group.close)),
6987
+ keyExpression: null
6640
6988
  };
6641
6989
  }
6642
6990
  return null;
6643
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
+ }
6644
7015
  function parameterExpression(parameters) {
6645
7016
  const names = parameters.split(",").map((part) => /^\s*(?:\.\.\.)?([A-Za-z_$][\w$]*)/.exec(part)?.[1]).filter((name) => name !== void 0);
6646
7017
  const named = names.find((name) => CHECKPOINT_ARG_PROPERTIES.includes(name));
@@ -6650,14 +7021,6 @@ function parameterExpression(parameters) {
6650
7021
  function camelOf(key) {
6651
7022
  return key.replace(/_([a-z0-9])/g, (_match, char) => char.toUpperCase());
6652
7023
  }
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
7024
  function argumentsOf(source, object) {
6662
7025
  for (const property of CHECKPOINT_ARG_PROPERTIES) {
6663
7026
  const pattern = new RegExp(`\\b${escapeForRegex(object)}\\s*(?:\\?\\.|\\.)\\s*${property}\\b`);
@@ -6675,63 +7038,6 @@ function blockIndent(source, at) {
6675
7038
  const lineStart = source.lastIndexOf("\n", at) + 1;
6676
7039
  return `${/^[ \t]*/.exec(source.slice(lineStart, at))?.[0] ?? ""} `;
6677
7040
  }
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
7041
  function unguardJs(source) {
6736
7042
  const checkpoints = uncheckpointJs(source);
6737
7043
  const imported = jsGuardImport(checkpoints.after);
@@ -6788,9 +7094,10 @@ function uncheckpointJs(source) {
6788
7094
  const lineOf = lineLocator2(source);
6789
7095
  const leftovers = [];
6790
7096
  let removed = 0;
7097
+ const key = `(?:(['"\`])[a-z][a-z0-9_]*\\1|[A-Za-z_$][\\w$]*(?:\\s*(?:\\?\\.|\\.)\\s*[A-Za-z_$][\\w$]*)+)`;
6791
7098
  let after = source.replace(
6792
7099
  new RegExp(
6793
- `^[ \\t]*(?:await[ \\t]+)?${escapeForRegex(alias)}\\(\\s*(['"\`])[a-z][a-z0-9_]*\\1[^\\n]*\\)[ \\t]*;?[ \\t]*\\r?\\n`,
7100
+ `^[ \\t]*(?:await[ \\t]+)?${escapeForRegex(alias)}\\(\\s*${key}[^\\n]*\\)[ \\t]*;?[ \\t]*\\r?\\n`,
6794
7101
  "gm"
6795
7102
  ),
6796
7103
  () => {
@@ -6812,8 +7119,28 @@ function uncheckpointJs(source) {
6812
7119
  function unguardPython(source) {
6813
7120
  const imported = /^from[ \t]+veefore[ \t]+import[ \t]+([^\n]+)$/m.exec(source);
6814
7121
  if (!imported) return { after: source, removed: 0, leftovers: [] };
6815
- const spec = (imported[1] ?? "").replace(/[()]/g, "").split(",").map((part) => /^guard(?:\s+as\s+(\w+))?$/.exec(part.trim())).find((found) => found !== null);
6816
- if (!spec) return { after: source, removed: 0, leftovers: [] };
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
+ }
6817
7144
  const alias = spec[1] ?? "guard";
6818
7145
  const lineOf = lineLocator2(source);
6819
7146
  let removed = 0;
@@ -6852,18 +7179,20 @@ function unguardPython(source) {
6852
7179
  after = applyEdits(after, edits);
6853
7180
  removed += edits.length;
6854
7181
  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
- }
7182
+ if (!stillUsed) after = removePythonImport(after, "guard");
6865
7183
  return { after, removed, leftovers };
6866
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
+ }
6867
7196
  function arrowExpressionBody(fn) {
6868
7197
  let cursor = 0;
6869
7198
  if (fn.startsWith("(")) {
@@ -7107,7 +7436,6 @@ async function integrate(root, toolKeys, options) {
7107
7436
  const candidates = shortlist(root, index, anchors);
7108
7437
  const sourceFiles = candidates.files;
7109
7438
  const truncated = index.truncated || candidates.truncated;
7110
- const plan = planRewrite(root, sourceFiles, toolKeys);
7111
7439
  const interactive = options.interactive ?? (process.stdin.isTTY === true && process.stdout.isTTY === true && !options.yes);
7112
7440
  if (truncated && !options.quiet) {
7113
7441
  out(
@@ -7118,13 +7446,39 @@ async function integrate(root, toolKeys, options) {
7118
7446
  const dispatchGuardFiles = findDispatchGuards(sources);
7119
7447
  const dispatchGuarded = dispatchGuardFiles.length > 0;
7120
7448
  const alreadyGuarded = guardedToolKeys(sources, toolKeys);
7121
- for (const file of plan.files) {
7122
- for (const change of file.changes) {
7123
- if (change.status === "manual" && alreadyGuarded.has(change.key)) {
7124
- change.status = "already";
7125
- delete change.reason;
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
+ }
7126
7456
  }
7127
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
+ );
7128
7482
  }
7129
7483
  let manual = plan.files.flatMap(
7130
7484
  (file) => file.changes.filter((change) => change.status === "manual").map((change) => ({
@@ -7270,7 +7624,15 @@ async function integrate(root, toolKeys, options) {
7270
7624
  reportSdkReadiness(root, [.../* @__PURE__ */ new Set([...guardedFiles, ...dispatchGuardFiles])]);
7271
7625
  }
7272
7626
  const unguarded2 = reportProtection(root, sourceFiles, toolKeys, quiet);
7273
- return { status: "nothing-to-do", plan, checks: [], backupId: null, manual, unguarded: unguarded2 };
7627
+ return {
7628
+ status: "nothing-to-do",
7629
+ plan,
7630
+ checks: [],
7631
+ backupId: null,
7632
+ manual,
7633
+ unguarded: unguarded2,
7634
+ skipped
7635
+ };
7274
7636
  }
7275
7637
  if (options.dryRun) {
7276
7638
  out(style.grey("Dry run: nothing was written."));
@@ -7280,7 +7642,8 @@ async function integrate(root, toolKeys, options) {
7280
7642
  checks: [],
7281
7643
  backupId: null,
7282
7644
  manual,
7283
- unguarded: reportProtection(root, sourceFiles, toolKeys, true)
7645
+ unguarded: reportProtection(root, sourceFiles, toolKeys, true),
7646
+ skipped
7284
7647
  };
7285
7648
  }
7286
7649
  if (!options.quiet) {
@@ -7301,7 +7664,8 @@ async function integrate(root, toolKeys, options) {
7301
7664
  checks: [],
7302
7665
  backupId: null,
7303
7666
  manual,
7304
- unguarded: reportProtection(root, sourceFiles, toolKeys, true)
7667
+ unguarded: reportProtection(root, sourceFiles, toolKeys, true),
7668
+ skipped
7305
7669
  };
7306
7670
  }
7307
7671
  const jsReady = languages.has("js") && sdkInstalled(root, "js");
@@ -7318,12 +7682,14 @@ async function integrate(root, toolKeys, options) {
7318
7682
  checks: [],
7319
7683
  backupId: null,
7320
7684
  manual,
7321
- unguarded: reportProtection(root, sourceFiles, toolKeys, true)
7685
+ unguarded: reportProtection(root, sourceFiles, toolKeys, true),
7686
+ skipped
7322
7687
  };
7323
7688
  }
7324
7689
  }
7325
7690
  const backup = createBackup(root, toWrite);
7326
7691
  for (const file of toWrite) writeFilePreservingMode(path10.join(root, file.file), file.after);
7692
+ forgetIndex(root);
7327
7693
  out(
7328
7694
  `${symbol.pass} Updated ${toWrite.length} file${toWrite.length === 1 ? "" : "s"} ${style.grey(`(backup ${backup.id})`)}`
7329
7695
  );
@@ -7385,7 +7751,8 @@ async function integrate(root, toolKeys, options) {
7385
7751
  checks,
7386
7752
  backupId: null,
7387
7753
  manual,
7388
- unguarded: reportProtection(root, sourceFiles, toolKeys, true)
7754
+ unguarded: reportProtection(root, sourceFiles, toolKeys, true),
7755
+ skipped
7389
7756
  };
7390
7757
  }
7391
7758
  for (const language of languages) {
@@ -7395,9 +7762,14 @@ async function integrate(root, toolKeys, options) {
7395
7762
  );
7396
7763
  }
7397
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
+ }
7398
7770
  const unguarded = reportProtection(root, sourceFiles, toolKeys, options.quiet === true);
7399
7771
  out(style.grey(" Undo at any time with `veefore undo`."));
7400
- return { status: "applied", plan, checks, backupId: backup.id, manual, unguarded };
7772
+ return { status: "applied", plan, checks, backupId: backup.id, manual, unguarded, skipped };
7401
7773
  }
7402
7774
  function reportProtection(root, files, toolKeys, quiet) {
7403
7775
  const guarded = guardedToolKeys(readSources(root, files), toolKeys);
@@ -7560,6 +7932,43 @@ function stillManual(items, plan) {
7560
7932
  return true;
7561
7933
  });
7562
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
+ }
7563
7972
  function checkpointUsable(root, quiet) {
7564
7973
  const sdks = installedSdks(root).filter((sdk) => sdk.language === "js");
7565
7974
  const stale = sdks.filter((sdk) => !sdk.hasCheckpoint);
@@ -7632,7 +8041,13 @@ function guardedToolKeys(sources, toolKeys) {
7632
8041
  return new Map([...guarded].filter(([key]) => toolKeys.includes(key)));
7633
8042
  }
7634
8043
  function checkIntegration(root, toolKeys) {
7635
- const guarded = guardedToolKeys(readSources(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);
7636
8051
  heading("Protection check");
7637
8052
  for (const key of toolKeys) {
7638
8053
  const file = guarded.get(key);
@@ -7934,10 +8349,20 @@ async function integrateCommand(args, flags) {
7934
8349
  let assist;
7935
8350
  let dryRun = false;
7936
8351
  let check = false;
8352
+ let tools;
7937
8353
  for (const arg of args) {
7938
8354
  if (arg === "--dry-run") dryRun = true;
7939
8355
  else if (arg === "--check") check = true;
7940
- else if (arg.startsWith("--assist=")) {
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=")) {
7941
8366
  const value = ASSIST_VALUES[arg.slice("--assist=".length)];
7942
8367
  if (!value) {
7943
8368
  throw new CliError(
@@ -7980,7 +8405,8 @@ async function integrateCommand(args, flags) {
7980
8405
  dryRun,
7981
8406
  quiet: flags.json,
7982
8407
  assist,
7983
- api
8408
+ api,
8409
+ ...tools ? { tools } : {}
7984
8410
  });
7985
8411
  if (flags.json) {
7986
8412
  out(
@@ -7992,6 +8418,7 @@ async function integrateCommand(args, flags) {
7992
8418
  not_found: outcome.plan.notFound,
7993
8419
  manual: outcome.manual,
7994
8420
  unguarded: outcome.unguarded,
8421
+ skipped: outcome.skipped,
7995
8422
  checks: outcome.checks
7996
8423
  },
7997
8424
  null,
@@ -8394,7 +8821,7 @@ import { homedir as homedir2 } from "node:os";
8394
8821
  import path12 from "node:path";
8395
8822
 
8396
8823
  // src/version.ts
8397
- var CLI_VERSION = "1.5.0";
8824
+ var CLI_VERSION = "1.6.0";
8398
8825
 
8399
8826
  // src/panel.ts
8400
8827
  function welcomeSource(flags = {}) {
@@ -8518,12 +8945,12 @@ function printScan(scan) {
8518
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)")}`
8519
8946
  );
8520
8947
  }
8521
- printGaps(scan.root);
8948
+ printGaps(scan);
8522
8949
  }
8523
- function printGaps(root) {
8950
+ function printGaps(scan) {
8524
8951
  let discovery;
8525
8952
  try {
8526
- discovery = discoverAgents(root);
8953
+ discovery = discoverAgents(scan.root, scan);
8527
8954
  } catch {
8528
8955
  return;
8529
8956
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "veefore",
3
- "version": "1.5.0",
3
+ "version": "1.6.0",
4
4
  "description": "Veefore command line: sign in, scan an agent project, set it up and protect its tools.",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://veefore.com",