@appilots/cli 0.11.3 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,13 +1,13 @@
1
1
  'use strict';
2
2
 
3
3
  var fs = require('fs/promises');
4
- var path2 = require('path');
5
- var traverse4 = require('@babel/traverse');
4
+ var crypto = require('crypto');
5
+ var traverse5 = require('@babel/traverse');
6
6
  var BabelTypes = require('@babel/types');
7
+ var path2 = require('path');
7
8
  var fastGlob = require('fast-glob');
8
9
  var parser = require('@babel/parser');
9
10
  var fs$1 = require('fs');
10
- var crypto = require('crypto');
11
11
 
12
12
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
13
13
 
@@ -30,9 +30,9 @@ function _interopNamespace(e) {
30
30
  }
31
31
 
32
32
  var fs__default = /*#__PURE__*/_interopDefault(fs);
33
- var path2__namespace = /*#__PURE__*/_interopNamespace(path2);
34
- var traverse4__default = /*#__PURE__*/_interopDefault(traverse4);
33
+ var traverse5__default = /*#__PURE__*/_interopDefault(traverse5);
35
34
  var BabelTypes__namespace = /*#__PURE__*/_interopNamespace(BabelTypes);
35
+ var path2__namespace = /*#__PURE__*/_interopNamespace(path2);
36
36
  var fastGlob__default = /*#__PURE__*/_interopDefault(fastGlob);
37
37
  var parser__namespace = /*#__PURE__*/_interopNamespace(parser);
38
38
 
@@ -41,6 +41,343 @@ var __export = (target, all) => {
41
41
  for (var name in all)
42
42
  __defProp(target, name, { get: all[name], enumerable: true });
43
43
  };
44
+ var MAX_HOPS = 8;
45
+ function unwrap(path11) {
46
+ while (path11.isTSAsExpression() || path11.isTSTypeAssertion() || path11.isTSNonNullExpression() || path11.isTSSatisfiesExpression() || path11.isParenthesizedExpression())
47
+ path11 = path11.get("expression");
48
+ return path11;
49
+ }
50
+ function constantValue(path11, depth = 0) {
51
+ if (depth > MAX_HOPS) return void 0;
52
+ path11 = unwrap(path11);
53
+ if (!path11.isIdentifier()) return path11;
54
+ const binding = path11.scope.getBinding(path11.node.name);
55
+ if (!binding?.constant || !binding.path.isVariableDeclarator()) return path11;
56
+ const init = binding.path.get("init");
57
+ const resolved = init.node ? constantValue(init, depth + 1) : void 0;
58
+ if (resolved?.isObjectExpression() && binding.referencePaths.some(
59
+ (reference) => !reference.parentPath?.isJSXSpreadAttribute() && !reference.parentPath?.isSpreadElement()
60
+ ))
61
+ return void 0;
62
+ return resolved;
63
+ }
64
+ function propValue(path11, name) {
65
+ const readObject = (path12) => {
66
+ const resolved = constantValue(path12);
67
+ if (!resolved?.isObjectExpression()) return { blocked: true };
68
+ for (const property of [...resolved.get("properties")].reverse()) {
69
+ if (property.isSpreadElement()) {
70
+ const found = readObjectBounded(property.get("argument"));
71
+ if (found.value || found.blocked) return found;
72
+ } else if (property.isObjectProperty() || property.isObjectMethod()) {
73
+ if (property.node.computed) return { blocked: true };
74
+ const key = property.node.key;
75
+ if ((BabelTypes__namespace.isIdentifier(key) ? key.name : BabelTypes__namespace.isStringLiteral(key) ? key.value : "") === name)
76
+ return property.isObjectProperty() ? { value: property.get("value") } : { value: property };
77
+ }
78
+ }
79
+ return {};
80
+ };
81
+ let objectBudget = MAX_HOPS;
82
+ const readObjectBounded = (path12) => objectBudget-- > 0 ? readObject(path12) : { blocked: true };
83
+ for (const attr of [...path11.get("attributes")].reverse()) {
84
+ if (attr.isJSXAttribute() && BabelTypes__namespace.isJSXIdentifier(attr.node.name, { name })) {
85
+ const value = attr.get("value");
86
+ return value.isJSXExpressionContainer() ? unwrap(value.get("expression")) : value.node ? value : void 0;
87
+ }
88
+ if (attr.isJSXSpreadAttribute()) {
89
+ const found = readObjectBounded(attr.get("argument"));
90
+ if (found.value || found.blocked) return found.value;
91
+ }
92
+ }
93
+ return void 0;
94
+ }
95
+ function importIdentity(path11, depth = 0) {
96
+ if (depth > MAX_HOPS) return void 0;
97
+ path11 = unwrap(path11);
98
+ if (path11.isIdentifier() || path11.isJSXIdentifier()) {
99
+ const binding = path11.scope.getBinding(path11.node.name);
100
+ if (!binding?.constant) return void 0;
101
+ const declaration = binding.path;
102
+ if (declaration.parentPath?.isImportDeclaration()) {
103
+ const module = declaration.parentPath.node.source.value;
104
+ if (declaration.isImportSpecifier()) {
105
+ const imported = declaration.node.imported;
106
+ return { module, imported: BabelTypes__namespace.isIdentifier(imported) ? imported.name : imported.value };
107
+ }
108
+ if (declaration.isImportDefaultSpecifier()) return { module, imported: "default" };
109
+ if (declaration.isImportNamespaceSpecifier()) return { module, imported: "*" };
110
+ }
111
+ if (declaration.isVariableDeclarator() && declaration.get("init").node)
112
+ return importIdentity(declaration.get("init"), depth + 1);
113
+ }
114
+ if (path11.isJSXMemberExpression() || path11.isMemberExpression() && !path11.node.computed) {
115
+ const origin = importIdentity(path11.get("object"), depth + 1);
116
+ const key = path11.node.property;
117
+ if (origin && (BabelTypes__namespace.isIdentifier(key) || BabelTypes__namespace.isJSXIdentifier(key)) && (origin.imported === "*" || origin.module === "react" && origin.imported === "default"))
118
+ return { module: origin.module, imported: key.name };
119
+ }
120
+ return void 0;
121
+ }
122
+ function handlerFunction(path11, depth = 0) {
123
+ if (depth > MAX_HOPS) return void 0;
124
+ path11 = unwrap(path11);
125
+ if (path11.isFunction()) return path11;
126
+ if (path11.isIdentifier()) {
127
+ const binding = path11.scope.getBinding(path11.node.name);
128
+ if (!binding?.constant) return void 0;
129
+ if (binding.path.isFunctionDeclaration()) return binding.path;
130
+ if (binding.path.isVariableDeclarator() && binding.path.get("init").node)
131
+ return handlerFunction(binding.path.get("init"), depth + 1);
132
+ }
133
+ if (path11.isCallExpression()) {
134
+ const origin = importIdentity(path11.get("callee"));
135
+ if (origin?.module === "react" && origin.imported === "useCallback") {
136
+ const callback = path11.get("arguments")[0];
137
+ if (callback) return handlerFunction(callback, depth + 1);
138
+ }
139
+ }
140
+ if (path11.isMemberExpression() && !path11.node.computed && BabelTypes__namespace.isThisExpression(path11.node.object)) {
141
+ const key = path11.node.property;
142
+ if (!BabelTypes__namespace.isIdentifier(key)) return void 0;
143
+ const owner = path11.findParent((p) => p.isClassDeclaration() || p.isClassExpression());
144
+ if (!owner || !(owner.isClassDeclaration() || owner.isClassExpression())) return void 0;
145
+ for (const member of owner.get("body").get("body")) {
146
+ if (!(member.isClassMethod() || member.isClassProperty()) || member.node.computed || member.node.static)
147
+ continue;
148
+ if (!BabelTypes__namespace.isIdentifier(member.node.key, { name: key.name })) continue;
149
+ if (member.isClassMethod() && member.node.kind === "method") return member;
150
+ if (member.isClassProperty() && member.get("value").node)
151
+ return handlerFunction(member.get("value"), depth + 1);
152
+ }
153
+ }
154
+ return void 0;
155
+ }
156
+
157
+ // src/extractors/control-evidence.ts
158
+ function symbol(node) {
159
+ if (BabelTypes__namespace.isTSAsExpression(node) || BabelTypes__namespace.isTSTypeAssertion(node) || BabelTypes__namespace.isTSNonNullExpression(node) || BabelTypes__namespace.isTSSatisfiesExpression(node))
160
+ return symbol(node.expression);
161
+ if (BabelTypes__namespace.isIdentifier(node) || BabelTypes__namespace.isJSXIdentifier(node)) return node.name;
162
+ if (BabelTypes__namespace.isThisExpression(node)) return "this";
163
+ if (BabelTypes__namespace.isMemberExpression(node) && !node.computed || BabelTypes__namespace.isJSXMemberExpression(node)) {
164
+ const object = symbol(node.object), property = symbol(node.property);
165
+ return object && property ? `${object}.${property}` : void 0;
166
+ }
167
+ if (BabelTypes__namespace.isUnaryExpression(node) && node.operator === "!") {
168
+ const value = symbol(node.argument);
169
+ return value ? `!${value}` : void 0;
170
+ }
171
+ return void 0;
172
+ }
173
+ function attribute(node, name) {
174
+ return node.attributes.find(
175
+ (a) => BabelTypes__namespace.isJSXAttribute(a) && BabelTypes__namespace.isJSXIdentifier(a.name, { name })
176
+ );
177
+ }
178
+ var ICON_LIBRARIES = [
179
+ "lucide-react-native",
180
+ "lucide-react",
181
+ "@tamagui/lucide-icons",
182
+ "@expo/vector-icons",
183
+ "@react-native-vector-icons/",
184
+ "react-native-vector-icons/"
185
+ ];
186
+ function iconLibrary(module) {
187
+ return ICON_LIBRARIES.some(
188
+ (name) => name.endsWith("/") ? module.startsWith(name) : module === name || module.startsWith(name + "/")
189
+ );
190
+ }
191
+ function add(values, value, max = 12) {
192
+ if (value && value.length <= 240 && values.length < max && !values.includes(value))
193
+ values.push(value);
194
+ }
195
+ function iconName(path11, explicitIcon = false) {
196
+ const origin = importIdentity(path11);
197
+ if (origin && iconLibrary(origin.module) && origin.imported !== "*")
198
+ return origin.imported === "default" ? origin.module : `${origin.module}:${origin.imported}`;
199
+ const name = symbol(path11.node);
200
+ return name && (explicitIcon || /icon/i.test(name)) ? name : void 0;
201
+ }
202
+ function iconsInElement(path11, explicitIcon = false) {
203
+ const name = iconName(path11.get("name"), explicitIcon);
204
+ if (!name) return void 0;
205
+ const glyph = propValue(path11, "name");
206
+ const value = glyph && constantValue(glyph);
207
+ return value?.isStringLiteral() ? `${name}:${value.node.value}` : name;
208
+ }
209
+ function hasInteraction(path11) {
210
+ return ["onPress", "onLongPress", "onClick"].some(
211
+ (name) => attribute(path11.node, name) || propValue(path11, name)
212
+ );
213
+ }
214
+ function collectPresentationIcons(path11, icons) {
215
+ const namedIconProps = [
216
+ "icon",
217
+ "prefix",
218
+ "suffix",
219
+ "left",
220
+ "right",
221
+ "leadingIcon",
222
+ "trailingIcon",
223
+ "startIcon",
224
+ "endIcon",
225
+ "renderIcon"
226
+ ];
227
+ const props = new Set(namedIconProps);
228
+ for (const attr of path11.node.attributes)
229
+ if (BabelTypes__namespace.isJSXAttribute(attr) && BabelTypes__namespace.isJSXIdentifier(attr.name) && !/^on[A-Z]/.test(attr.name.name))
230
+ props.add(attr.name.name);
231
+ for (const prop of props) {
232
+ const icon = propValue(path11, prop);
233
+ if (icon) {
234
+ const value = constantValue(icon);
235
+ if (value?.isStringLiteral()) {
236
+ if (prop.toLowerCase().includes("icon")) add(icons, value.node.value, 8);
237
+ } else {
238
+ const explicitIcon = prop.toLowerCase() === "icon";
239
+ if (icon.isJSXElement()) {
240
+ if (hasInteraction(icon.get("openingElement"))) continue;
241
+ add(icons, iconsInElement(icon.get("openingElement"), explicitIcon), 8);
242
+ }
243
+ icon.traverse({
244
+ JSXAttribute(attr) {
245
+ if (BabelTypes__namespace.isJSXIdentifier(attr.node.name) && /^on[A-Z]/.test(attr.node.name.name))
246
+ attr.skip();
247
+ },
248
+ JSXElement(child) {
249
+ const opening = child.get("openingElement");
250
+ if (hasInteraction(opening)) {
251
+ child.skip();
252
+ return;
253
+ }
254
+ add(icons, iconsInElement(opening, explicitIcon), 8);
255
+ }
256
+ });
257
+ if (!icons.length && (namedIconProps.includes(prop) || explicitIcon))
258
+ add(icons, iconName(icon, explicitIcon), 8);
259
+ }
260
+ }
261
+ }
262
+ }
263
+ function collectIcons(path11) {
264
+ const icons = [];
265
+ collectPresentationIcons(path11, icons);
266
+ const jsx = path11.parentPath;
267
+ if (jsx.isJSXElement())
268
+ jsx.traverse({
269
+ // JSX mentioned inside an event callback is not a rendered child icon.
270
+ JSXAttribute(attr) {
271
+ attr.skip();
272
+ },
273
+ JSXElement(child) {
274
+ const opening = child.get("openingElement");
275
+ if (hasInteraction(opening)) {
276
+ child.skip();
277
+ return;
278
+ }
279
+ add(icons, iconsInElement(opening), 8);
280
+ collectPresentationIcons(opening, icons);
281
+ }
282
+ });
283
+ return icons;
284
+ }
285
+ function conditionsAt(path11) {
286
+ const conditions = [];
287
+ let child = path11;
288
+ for (let parent = child.parentPath; parent && !parent.isFunction(); child = parent, parent = parent.parentPath) {
289
+ let test;
290
+ let negated = false;
291
+ if (parent.isLogicalExpression() && parent.node.right === child.node) {
292
+ if (parent.node.operator === "&&") test = parent.node.left;
293
+ if (parent.node.operator === "||") {
294
+ test = parent.node.left;
295
+ negated = true;
296
+ }
297
+ } else if (parent.isConditionalExpression() && parent.node.test !== child.node) {
298
+ test = parent.node.test;
299
+ negated = parent.node.alternate === child.node;
300
+ } else if (parent.isIfStatement() && parent.node.test !== child.node) {
301
+ test = parent.node.test;
302
+ negated = parent.node.alternate === child.node;
303
+ }
304
+ const name = symbol(test);
305
+ if (name) add(conditions, negated ? name.startsWith("!") ? name.slice(1) : `!${name}` : name);
306
+ }
307
+ return conditions;
308
+ }
309
+ function collectHandlerEvidence(expression, evidence) {
310
+ const seen = /* @__PURE__ */ new Set();
311
+ let budget = 100;
312
+ const visit = (path11, depth) => {
313
+ if (depth > 4 || budget-- <= 0) return;
314
+ const fn = handlerFunction(path11);
315
+ if (!fn || seen.has(fn.node)) return;
316
+ seen.add(fn.node);
317
+ fn.traverse({
318
+ // Ignore uncalled helper definitions; inline callbacks remain source evidence.
319
+ Function(nested) {
320
+ if (!nested.parentPath.isCallExpression() && !nested.parentPath.isObjectProperty())
321
+ nested.skip();
322
+ },
323
+ CallExpression(call) {
324
+ if (budget-- <= 0) {
325
+ call.skip();
326
+ return;
327
+ }
328
+ add(evidence.calls, symbol(call.node.callee));
329
+ for (const arg of call.node.arguments) add(evidence.argumentBindings, symbol(arg));
330
+ visit(call.get("callee"), depth + 1);
331
+ if (!BabelTypes__namespace.isMemberExpression(call.node.callee) || call.node.callee.computed || !BabelTypes__namespace.isIdentifier(call.node.callee.property, { name: "alert" }))
332
+ return;
333
+ const callee = call.get("callee");
334
+ if (!callee.isMemberExpression()) return;
335
+ const origin = importIdentity(callee.get("object"));
336
+ if (origin?.module !== "react-native" || origin.imported !== "Alert") return;
337
+ const options = call.node.arguments[2];
338
+ const destructiveOption = BabelTypes__namespace.isArrayExpression(options) && options.elements.some(
339
+ (option) => BabelTypes__namespace.isObjectExpression(option) && option.properties.some(
340
+ (p) => BabelTypes__namespace.isObjectProperty(p) && !p.computed && symbol(p.key) === "style" && BabelTypes__namespace.isStringLiteral(p.value, { value: "destructive" })
341
+ )
342
+ );
343
+ if (destructiveOption)
344
+ evidence.nativeConfirmation = {
345
+ title: symbol(call.node.arguments[0]),
346
+ destructiveOption
347
+ };
348
+ }
349
+ });
350
+ };
351
+ visit(unwrap(expression), 0);
352
+ }
353
+ function extractControlEvidence(ast, source, file) {
354
+ const candidates = [];
355
+ const sourceHash = crypto.createHash("sha256").update(source).digest("hex");
356
+ traverse5__default.default(ast, {
357
+ JSXOpeningElement(path11) {
358
+ const node = path11.node;
359
+ if (attribute(node, "__appilotsControl")) return;
360
+ const value = propValue(path11, "onPress");
361
+ if (!value && !attribute(node, "onPress") || value?.isNullLiteral() || value?.isJSXEmptyExpression())
362
+ return;
363
+ const icons = collectIcons(path11);
364
+ if (!icons.length || node.end == null) return;
365
+ const evidence = {
366
+ version: 1,
367
+ siteId: crypto.createHash("sha256").update(`${file}:${sourceHash}:${node.start}`).digest("hex").slice(0, 20),
368
+ component: symbol(node.name) ?? "unknown",
369
+ icons,
370
+ ...value && symbol(value.node) ? { handler: symbol(value.node) } : {},
371
+ calls: [],
372
+ argumentBindings: [],
373
+ conditions: conditionsAt(path11)
374
+ };
375
+ if (value) collectHandlerEvidence(value, evidence);
376
+ candidates.push({ evidence, sourceHash, offset: node.end - (node.selfClosing ? 2 : 1) });
377
+ }
378
+ });
379
+ return candidates;
380
+ }
44
381
  function byCodeUnit(a, b) {
45
382
  return a < b ? -1 : a > b ? 1 : 0;
46
383
  }
@@ -162,7 +499,7 @@ function classifyJsxComponent(name, element) {
162
499
  }
163
500
  function collectFunctions(ast) {
164
501
  const handlers = /* @__PURE__ */ new Map();
165
- traverse4__default.default(ast, {
502
+ traverse5__default.default(ast, {
166
503
  FunctionDeclaration: (nodePath) => {
167
504
  if (nodePath.node.id?.name) handlers.set(nodePath.node.id.name, nodePath.node);
168
505
  },
@@ -265,7 +602,7 @@ function analyzeFunctionBehavior(name, fn, handlers, seen = /* @__PURE__ */ new
265
602
  }
266
603
  };
267
604
  if (fn.body) {
268
- traverse4__default.default(fn.body, {
605
+ traverse5__default.default(fn.body, {
269
606
  noScope: true,
270
607
  enter: (nodePath) => inspectNode(nodePath.node)
271
608
  });
@@ -293,7 +630,7 @@ function setterToStateName(setterName) {
293
630
  }
294
631
  function extractNavigationCalls(ast) {
295
632
  const calls = [];
296
- traverse4__default.default(ast, {
633
+ traverse5__default.default(ast, {
297
634
  noScope: !BabelTypes__namespace.isFile(ast),
298
635
  CallExpression: (nodePath) => {
299
636
  const node = nodePath.node;
@@ -444,6 +781,7 @@ var ScreenAnalyzer = class {
444
781
  routeTargetFiles;
445
782
  /** §D: Count of screens filtered out in strict mode (available after analyze()) */
446
783
  screensFilteredOut = 0;
784
+ controlEvidenceFiles = {};
447
785
  constructor(config, options) {
448
786
  this.config = config;
449
787
  this.routeTargetFiles = options?.routeTargetFiles ?? /* @__PURE__ */ new Set();
@@ -544,6 +882,11 @@ var ScreenAnalyzer = class {
544
882
  title: registerScreenMeta?.title,
545
883
  description: registerScreenMeta?.description,
546
884
  components,
885
+ controlCandidates: extractControlEvidence(
886
+ ast,
887
+ source,
888
+ path2__namespace.default.relative(this.config.rootDir, filePath)
889
+ ),
547
890
  forms,
548
891
  actions,
549
892
  navigationTargets,
@@ -554,6 +897,8 @@ var ScreenAnalyzer = class {
554
897
  ...permissionsFromJsDoc.isPii ? { isPii: true } : {}
555
898
  } : {}
556
899
  };
900
+ if (descriptor.controlCandidates?.length)
901
+ this.controlEvidenceFiles[path2__namespace.default.relative(this.config.rootDir, filePath).split(path2__namespace.default.sep).join("/")] = descriptor.controlCandidates;
557
902
  descriptor.__hasRegisterScreen = hasRegisterScreenCall;
558
903
  return descriptor;
559
904
  }
@@ -563,7 +908,7 @@ var ScreenAnalyzer = class {
563
908
  */
564
909
  detectRegisterScreenCall(ast) {
565
910
  let found = false;
566
- traverse4__default.default(ast, {
911
+ traverse5__default.default(ast, {
567
912
  CallExpression: (nodePath) => {
568
913
  if (found) return;
569
914
  const callee = nodePath.node.callee;
@@ -580,7 +925,7 @@ var ScreenAnalyzer = class {
580
925
  */
581
926
  extractRegisterScreenMetadata(ast) {
582
927
  let metadata = null;
583
- traverse4__default.default(ast, {
928
+ traverse5__default.default(ast, {
584
929
  CallExpression: (nodePath) => {
585
930
  const callee = nodePath.node.callee;
586
931
  if (BabelTypes__namespace.isIdentifier(callee) && callee.name === "registerScreen" || BabelTypes__namespace.isMemberExpression(callee) && BabelTypes__namespace.isIdentifier(callee.property) && callee.property.name === "registerScreen") {
@@ -877,7 +1222,7 @@ var ScreenAnalyzer = class {
877
1222
  */
878
1223
  extractDefaultComponentName(ast) {
879
1224
  let componentName = "";
880
- traverse4__default.default(ast, {
1225
+ traverse5__default.default(ast, {
881
1226
  ExportDefaultDeclaration: (nodePath) => {
882
1227
  const declaration = nodePath.node.declaration;
883
1228
  if (BabelTypes__namespace.isFunctionDeclaration(declaration) && declaration.id?.name) {
@@ -902,7 +1247,7 @@ var ScreenAnalyzer = class {
902
1247
  */
903
1248
  extractNavigationTargets(ast) {
904
1249
  const targets = /* @__PURE__ */ new Set();
905
- traverse4__default.default(ast, {
1250
+ traverse5__default.default(ast, {
906
1251
  CallExpression: (nodePath) => {
907
1252
  const callee = nodePath.node.callee;
908
1253
  if (BabelTypes__namespace.isMemberExpression(callee) && BabelTypes__namespace.isIdentifier(callee.object) && callee.object.name === "navigation" && BabelTypes__namespace.isIdentifier(callee.property) && callee.property.name === "navigate") {
@@ -921,7 +1266,7 @@ var ScreenAnalyzer = class {
921
1266
  extractForms(ast) {
922
1267
  const forms = [];
923
1268
  const fields = /* @__PURE__ */ new Map();
924
- traverse4__default.default(ast, {
1269
+ traverse5__default.default(ast, {
925
1270
  JSXOpeningElement: (nodePath) => {
926
1271
  const element = nodePath.node;
927
1272
  if (BabelTypes__namespace.isJSXIdentifier(element.name)) {
@@ -1042,7 +1387,7 @@ var ScreenAnalyzer = class {
1042
1387
  extractComponents(ast) {
1043
1388
  const components = [];
1044
1389
  const seen = /* @__PURE__ */ new Set();
1045
- traverse4__default.default(ast, {
1390
+ traverse5__default.default(ast, {
1046
1391
  JSXOpeningElement: (nodePath) => {
1047
1392
  const element = nodePath.node;
1048
1393
  if (BabelTypes__namespace.isJSXIdentifier(element.name)) {
@@ -1106,7 +1451,7 @@ var ScreenAnalyzer = class {
1106
1451
  const actionLabels = new Map(
1107
1452
  actions.filter((a) => a.label).map((a) => [this.normalizeLabel(a.label), a])
1108
1453
  );
1109
- traverse4__default.default(ast, {
1454
+ traverse5__default.default(ast, {
1110
1455
  JSXOpeningElement: (nodePath) => {
1111
1456
  const element = nodePath.node;
1112
1457
  if (BabelTypes__namespace.isJSXIdentifier(element.name)) {
@@ -1175,7 +1520,7 @@ var ScreenAnalyzer = class {
1175
1520
  }
1176
1521
  collectButtonHandlersByLabel(ast) {
1177
1522
  const out = /* @__PURE__ */ new Map();
1178
- traverse4__default.default(ast, {
1523
+ traverse5__default.default(ast, {
1179
1524
  JSXOpeningElement: (nodePath) => {
1180
1525
  const element = nodePath.node;
1181
1526
  if (!BabelTypes__namespace.isJSXIdentifier(element.name)) return;
@@ -1257,7 +1602,7 @@ var ScreenAnalyzer = class {
1257
1602
  }
1258
1603
  };
1259
1604
  if (fn.body) {
1260
- traverse4__default.default(
1605
+ traverse5__default.default(
1261
1606
  fn.body,
1262
1607
  {
1263
1608
  noScope: true,
@@ -1454,7 +1799,7 @@ var ScreenAnalyzer = class {
1454
1799
  extractCollections(ast) {
1455
1800
  const renderItemFns = this.collectRenderItemFunctions(ast);
1456
1801
  const collections = [];
1457
- traverse4__default.default(ast, {
1802
+ traverse5__default.default(ast, {
1458
1803
  JSXOpeningElement: (nodePath) => {
1459
1804
  const element = nodePath.node;
1460
1805
  if (!BabelTypes__namespace.isJSXIdentifier(element.name)) return;
@@ -1491,7 +1836,7 @@ var ScreenAnalyzer = class {
1491
1836
  }
1492
1837
  collectRenderItemFunctions(ast) {
1493
1838
  const out = /* @__PURE__ */ new Map();
1494
- traverse4__default.default(ast, {
1839
+ traverse5__default.default(ast, {
1495
1840
  VariableDeclarator: (nodePath) => {
1496
1841
  if (!BabelTypes__namespace.isIdentifier(nodePath.node.id)) return;
1497
1842
  const init = nodePath.node.init;
@@ -1534,7 +1879,7 @@ var ScreenAnalyzer = class {
1534
1879
  }
1535
1880
  extractRowAction(fn) {
1536
1881
  let action;
1537
- traverse4__default.default(
1882
+ traverse5__default.default(
1538
1883
  fn.body,
1539
1884
  {
1540
1885
  noScope: true,
@@ -1587,7 +1932,7 @@ var ScreenAnalyzer = class {
1587
1932
  } else if (BabelTypes__namespace.isIdentifier(firstParam)) {
1588
1933
  itemNames.add(firstParam.name);
1589
1934
  }
1590
- traverse4__default.default(
1935
+ traverse5__default.default(
1591
1936
  fn.body,
1592
1937
  {
1593
1938
  noScope: true,
@@ -1629,7 +1974,7 @@ var ScreenAnalyzer = class {
1629
1974
  inferSearchField(ast, dataSource) {
1630
1975
  if (!dataSource) return void 0;
1631
1976
  let queryBinding;
1632
- traverse4__default.default(ast, {
1977
+ traverse5__default.default(ast, {
1633
1978
  CallExpression: (nodePath) => {
1634
1979
  const node = nodePath.node;
1635
1980
  if (!BabelTypes__namespace.isMemberExpression(node.callee)) return;
@@ -1640,7 +1985,7 @@ var ScreenAnalyzer = class {
1640
1985
  const fn = node.arguments[0];
1641
1986
  if (!BabelTypes__namespace.isArrowFunctionExpression(fn) && !BabelTypes__namespace.isFunctionExpression(fn))
1642
1987
  return;
1643
- traverse4__default.default(
1988
+ traverse5__default.default(
1644
1989
  fn.body,
1645
1990
  {
1646
1991
  noScope: true,
@@ -1682,7 +2027,7 @@ var ScreenAnalyzer = class {
1682
2027
  }
1683
2028
  };
1684
2029
  var EXTENSIONS = [".tsx", ".ts", ".jsx", ".js", ".mjs", ".cjs"];
1685
- var MAX_HOPS = 8;
2030
+ var MAX_HOPS2 = 8;
1686
2031
  var ModuleGraph = class {
1687
2032
  asts = /* @__PURE__ */ new Map();
1688
2033
  resolved = /* @__PURE__ */ new Map();
@@ -1801,7 +2146,7 @@ var ModuleGraph = class {
1801
2146
  * Devolve o arquivo e o nome sob o qual ele é definido lá.
1802
2147
  */
1803
2148
  resolveBinding(file, name, hops = 0) {
1804
- if (hops > MAX_HOPS) return null;
2149
+ if (hops > MAX_HOPS2) return null;
1805
2150
  if (this.topLevelInit(file, name) !== null) return { file, name };
1806
2151
  const binding = this.imports(file).get(name);
1807
2152
  if (binding) {
@@ -1855,7 +2200,7 @@ var ModuleGraph = class {
1855
2200
  * com interpolação nem valor calculado, de propósito.
1856
2201
  */
1857
2202
  stringConstant(file, node, hops = 0) {
1858
- if (!node || hops > MAX_HOPS) return null;
2203
+ if (!node || hops > MAX_HOPS2) return null;
1859
2204
  if (BabelTypes__namespace.isStringLiteral(node)) return node.value;
1860
2205
  if (BabelTypes__namespace.isTemplateLiteral(node)) {
1861
2206
  return node.expressions.length === 0 ? node.quasis[0]?.value.cooked ?? null : null;
@@ -1884,7 +2229,7 @@ var ModuleGraph = class {
1884
2229
  * que é como o `pocketpal` monta todas as telas do Drawer).
1885
2230
  */
1886
2231
  componentFile(file, node, hops = 0) {
1887
- if (!node || hops > MAX_HOPS) return null;
2232
+ if (!node || hops > MAX_HOPS2) return null;
1888
2233
  if (BabelTypes__namespace.isCallExpression(node)) {
1889
2234
  for (const arg of node.arguments) {
1890
2235
  if (BabelTypes__namespace.isIdentifier(arg) || BabelTypes__namespace.isMemberExpression(arg)) {
@@ -2080,7 +2425,7 @@ function workspaceGlobs(dir) {
2080
2425
  }
2081
2426
  function expandWorkspaceGlobs(root, globs) {
2082
2427
  const out = [];
2083
- const add = (dir) => {
2428
+ const add2 = (dir) => {
2084
2429
  const pkgPath = path2__namespace.default.join(dir, "package.json");
2085
2430
  if (!fs$1.existsSync(pkgPath)) return;
2086
2431
  try {
@@ -2101,12 +2446,12 @@ function expandWorkspaceGlobs(root, globs) {
2101
2446
  for (const entry of entries) {
2102
2447
  const dir = path2__namespace.default.join(parent, entry);
2103
2448
  try {
2104
- if (fs$1.statSync(dir).isDirectory()) add(dir);
2449
+ if (fs$1.statSync(dir).isDirectory()) add2(dir);
2105
2450
  } catch {
2106
2451
  }
2107
2452
  }
2108
2453
  } else if (!glob.includes("*")) {
2109
- add(path2__namespace.default.join(root, glob));
2454
+ add2(path2__namespace.default.join(root, glob));
2110
2455
  }
2111
2456
  }
2112
2457
  return out.sort((a, b) => b.name.length - a.name.length);
@@ -2426,7 +2771,7 @@ var NavigationAnalyzer = class {
2426
2771
  const ast = this.graph.parse(filePath);
2427
2772
  if (!ast) return [];
2428
2773
  const out = /* @__PURE__ */ new Map();
2429
- traverse4__default.default(ast, {
2774
+ traverse5__default.default(ast, {
2430
2775
  CallExpression: (nodePath) => {
2431
2776
  const callee = nodePath.node.callee;
2432
2777
  if (!BabelTypes__namespace.isIdentifier(callee)) return;
@@ -2587,7 +2932,7 @@ var NavigationAnalyzer = class {
2587
2932
  const ast = this.graph.parse(filePath);
2588
2933
  if (!ast) return [];
2589
2934
  const found = /* @__PURE__ */ new Map();
2590
- traverse4__default.default(ast, {
2935
+ traverse5__default.default(ast, {
2591
2936
  JSXElement: (nodePath) => {
2592
2937
  const { node } = nodePath;
2593
2938
  const openingElement = node.openingElement;
@@ -2659,7 +3004,7 @@ var NavigationAnalyzer = class {
2659
3004
  if (!ast) return [];
2660
3005
  const byNavigator = /* @__PURE__ */ new Map();
2661
3006
  const resolved = /* @__PURE__ */ new Map();
2662
- traverse4__default.default(ast, {
3007
+ traverse5__default.default(ast, {
2663
3008
  JSXElement: (nodePath) => {
2664
3009
  const name = nodePath.node.openingElement.name;
2665
3010
  if (!BabelTypes__namespace.isJSXMemberExpression(name) || !BabelTypes__namespace.isJSXIdentifier(name.object)) return;
@@ -2892,7 +3237,7 @@ var NavigationAnalyzer = class {
2892
3237
  ...this.config.parserPlugins || []
2893
3238
  ]
2894
3239
  });
2895
- traverse4__default.default(ast, {
3240
+ traverse5__default.default(ast, {
2896
3241
  TSTypeAliasDeclaration: (nodePath) => {
2897
3242
  const { node } = nodePath;
2898
3243
  const typeName = node.id.name;
@@ -2965,7 +3310,7 @@ var NavigationAnalyzer = class {
2965
3310
  if (type.type === "TSUndefinedKeyword") return "undefined";
2966
3311
  if (type.type === "TSNullKeyword") return "null";
2967
3312
  if (type.type === "TSUnionType") {
2968
- return type.types.map((t13) => this.typeToString(t13)).join(" | ");
3313
+ return type.types.map((t15) => this.typeToString(t15)).join(" | ");
2969
3314
  }
2970
3315
  if (type.type === "TSTypeLiteral") {
2971
3316
  return "object";
@@ -2986,7 +3331,7 @@ var NavigationAnalyzer = class {
2986
3331
  /** Attach parsed type params to navigator screens */
2987
3332
  attachParamsToNavigators(navigators, types) {
2988
3333
  for (const navigator of navigators) {
2989
- const matchingType = types.find((t13) => t13.type === navigator.type);
3334
+ const matchingType = types.find((t15) => t15.type === navigator.type);
2990
3335
  if (matchingType) {
2991
3336
  for (const screen of navigator.screens) {
2992
3337
  const screenParams = matchingType.paramEntries.get(screen.name);
@@ -3075,7 +3420,7 @@ var ComponentAnalyzer = class {
3075
3420
  plugins: ["jsx", "typescript", ["decorators", { decoratorsBeforeExport: true }]]
3076
3421
  });
3077
3422
  const components = [];
3078
- traverse4__default.default(ast, {
3423
+ traverse5__default.default(ast, {
3079
3424
  JSXElement: (path11) => {
3080
3425
  const component = this.extractComponentFromJSXElement(path11.node);
3081
3426
  if (component) {
@@ -3203,12 +3548,12 @@ var FormAnalyzer = class {
3203
3548
  this.stateVariables.clear();
3204
3549
  this.inputElements = [];
3205
3550
  this.submitButtons = [];
3206
- traverse4__default.default(ast, {
3551
+ traverse5__default.default(ast, {
3207
3552
  CallExpression: (path11) => {
3208
3553
  this.extractStateVariables(path11.node);
3209
3554
  }
3210
3555
  });
3211
- traverse4__default.default(ast, {
3556
+ traverse5__default.default(ast, {
3212
3557
  JSXElement: (path11) => {
3213
3558
  this.extractFormElements(path11.node);
3214
3559
  }
@@ -3244,23 +3589,23 @@ var FormAnalyzer = class {
3244
3589
  for (const attr of openingElement.attributes) {
3245
3590
  if (BabelTypes__namespace.isJSXAttribute(attr) && BabelTypes__namespace.isJSXIdentifier(attr.name)) {
3246
3591
  const propName = attr.name.name;
3247
- const propValue = this.extractAttributeValue(attr.value);
3592
+ const propValue2 = this.extractAttributeValue(attr.value);
3248
3593
  switch (propName) {
3249
3594
  case "label":
3250
- info.label = propValue;
3595
+ info.label = propValue2;
3251
3596
  break;
3252
3597
  case "placeholder":
3253
- info.placeholder = propValue;
3598
+ info.placeholder = propValue2;
3254
3599
  break;
3255
3600
  case "keyboardType":
3256
- info.keyboardType = propValue;
3601
+ info.keyboardType = propValue2;
3257
3602
  break;
3258
3603
  case "testID":
3259
- info.testID = propValue;
3604
+ info.testID = propValue2;
3260
3605
  break;
3261
3606
  case "appilotsId":
3262
- info.appilotsId = propValue;
3263
- if (propValue) info.varName = propValue;
3607
+ info.appilotsId = propValue2;
3608
+ if (propValue2) info.varName = propValue2;
3264
3609
  break;
3265
3610
  case "value":
3266
3611
  if (attr.value && BabelTypes__namespace.isJSXExpressionContainer(attr.value) && BabelTypes__namespace.isIdentifier(attr.value.expression)) {
@@ -3309,7 +3654,7 @@ var FormAnalyzer = class {
3309
3654
  }
3310
3655
  extractValidationRules(ast) {
3311
3656
  const rules = {};
3312
- traverse4__default.default(ast, {
3657
+ traverse5__default.default(ast, {
3313
3658
  IfStatement: (path11) => {
3314
3659
  const test = path11.node.test;
3315
3660
  const rule = this.extractRuleFromCondition(test);
@@ -3463,7 +3808,7 @@ async function composeAffordances(options) {
3463
3808
  const exclusive = children.filter((c) => (fanIn.get(c) ?? 0) <= MAX_FAN_IN);
3464
3809
  if (exclusive.length === 0) continue;
3465
3810
  const actionIds = new Set(screen.actions.map((a) => a.id));
3466
- const targetIds = new Set((screen.targets ?? []).map((t13) => t13.id));
3811
+ const targetIds = new Set((screen.targets ?? []).map((t15) => t15.id));
3467
3812
  const formIds = new Set(screen.forms.map((f) => f.id));
3468
3813
  let gained = false;
3469
3814
  for (const file of exclusive) {
@@ -3605,6 +3950,7 @@ var ReactNativePlatformAnalyzer = class {
3605
3950
  });
3606
3951
  return {
3607
3952
  screens: enrichedScreens,
3953
+ controlEvidenceFiles: screenAnalyzer.controlEvidenceFiles,
3608
3954
  navigation,
3609
3955
  analyzedFiles: screenFiles.length,
3610
3956
  ...screenAnalyzer.screensFilteredOut > 0 ? { screensFilteredOut: screenAnalyzer.screensFilteredOut } : {},
@@ -3869,7 +4215,7 @@ function extractWebNavigationCalls(ast) {
3869
4215
  calls.push({ method: "navigate", targetPath: node.right.value });
3870
4216
  }
3871
4217
  };
3872
- traverse4__default.default(ast, {
4218
+ traverse5__default.default(ast, {
3873
4219
  noScope: !BabelTypes__namespace.isFile(ast),
3874
4220
  enter: (nodePath) => inspect(nodePath.node)
3875
4221
  });
@@ -3991,7 +4337,7 @@ var WebScreenAnalyzer = class {
3991
4337
  // ── registerScreen ────────────────────────────────────────────────
3992
4338
  detectRegisterScreenCall(ast) {
3993
4339
  let found = false;
3994
- traverse4__default.default(ast, {
4340
+ traverse5__default.default(ast, {
3995
4341
  CallExpression: (nodePath) => {
3996
4342
  if (found) return;
3997
4343
  if (isRegisterScreenCallee(nodePath.node.callee)) {
@@ -4004,7 +4350,7 @@ var WebScreenAnalyzer = class {
4004
4350
  }
4005
4351
  extractRegisterScreenMetadata(ast) {
4006
4352
  let plain = null;
4007
- traverse4__default.default(ast, {
4353
+ traverse5__default.default(ast, {
4008
4354
  CallExpression: (nodePath) => {
4009
4355
  if (!isRegisterScreenCallee(nodePath.node.callee)) return;
4010
4356
  const arg = nodePath.node.arguments[0];
@@ -4052,7 +4398,7 @@ var WebScreenAnalyzer = class {
4052
4398
  extractComponentName(ast) {
4053
4399
  let defaultName = "";
4054
4400
  let firstExported = "";
4055
- traverse4__default.default(ast, {
4401
+ traverse5__default.default(ast, {
4056
4402
  ExportDefaultDeclaration: (nodePath) => {
4057
4403
  const declaration = nodePath.node.declaration;
4058
4404
  if (BabelTypes__namespace.isFunctionDeclaration(declaration) && declaration.id?.name) {
@@ -4081,7 +4427,7 @@ var WebScreenAnalyzer = class {
4081
4427
  extractComponents(ast) {
4082
4428
  const components = [];
4083
4429
  const seen = /* @__PURE__ */ new Set();
4084
- traverse4__default.default(ast, {
4430
+ traverse5__default.default(ast, {
4085
4431
  JSXOpeningElement: (nodePath) => {
4086
4432
  const element = nodePath.node;
4087
4433
  const name = getJsxElementName(element);
@@ -4102,7 +4448,7 @@ var WebScreenAnalyzer = class {
4102
4448
  // ── <label htmlFor> association ───────────────────────────────────
4103
4449
  collectHtmlForLabels(ast) {
4104
4450
  const labels = /* @__PURE__ */ new Map();
4105
- traverse4__default.default(ast, {
4451
+ traverse5__default.default(ast, {
4106
4452
  JSXElement: (nodePath) => {
4107
4453
  const element = nodePath.node;
4108
4454
  if (getJsxElementName(element.openingElement) !== "label") return;
@@ -4145,7 +4491,7 @@ var WebScreenAnalyzer = class {
4145
4491
  formBuckets.set(formElement, bucket);
4146
4492
  return bucket;
4147
4493
  };
4148
- traverse4__default.default(ast, {
4494
+ traverse5__default.default(ast, {
4149
4495
  JSXElement: (nodePath) => {
4150
4496
  const element = nodePath.node;
4151
4497
  const name = getJsxElementName(element.openingElement);
@@ -4162,7 +4508,7 @@ var WebScreenAnalyzer = class {
4162
4508
  if (!bucket.fields.has(field.name)) bucket.fields.set(field.name, field);
4163
4509
  }
4164
4510
  });
4165
- traverse4__default.default(ast, {
4511
+ traverse5__default.default(ast, {
4166
4512
  JSXElement: (nodePath) => {
4167
4513
  const element = nodePath.node;
4168
4514
  const name = getJsxElementName(element.openingElement);
@@ -4340,7 +4686,7 @@ var WebScreenAnalyzer = class {
4340
4686
  const actionLabels = new Map(
4341
4687
  actions.filter((a) => a.label).map((a) => [normalizeLabel(a.label), a])
4342
4688
  );
4343
- traverse4__default.default(ast, {
4689
+ traverse5__default.default(ast, {
4344
4690
  JSXElement: (nodePath) => {
4345
4691
  const element = nodePath.node;
4346
4692
  const name = getJsxElementName(element.openingElement);
@@ -4571,7 +4917,7 @@ var WebScreenAnalyzer = class {
4571
4917
  for (const call of extractWebNavigationCalls(ast)) {
4572
4918
  if (call.targetPath && call.targetPath.startsWith("/")) targets.add(call.targetPath);
4573
4919
  }
4574
- traverse4__default.default(ast, {
4920
+ traverse5__default.default(ast, {
4575
4921
  JSXOpeningElement: (nodePath) => {
4576
4922
  const element = nodePath.node;
4577
4923
  const name = getJsxElementName(element);
@@ -4591,7 +4937,7 @@ var WebScreenAnalyzer = class {
4591
4937
  extractCollections(ast) {
4592
4938
  const collections = [];
4593
4939
  const seen = /* @__PURE__ */ new Set();
4594
- traverse4__default.default(ast, {
4940
+ traverse5__default.default(ast, {
4595
4941
  JSXExpressionContainer: (nodePath) => {
4596
4942
  const expr = nodePath.node.expression;
4597
4943
  if (!BabelTypes__namespace.isCallExpression(expr) || !BabelTypes__namespace.isMemberExpression(expr.callee) || !BabelTypes__namespace.isIdentifier(expr.callee.object) || !BabelTypes__namespace.isIdentifier(expr.callee.property) || expr.callee.property.name !== "map") {
@@ -4703,7 +5049,7 @@ function firstCalledFunctionName(node) {
4703
5049
  }
4704
5050
  function containsWindowConfirm(body) {
4705
5051
  let found = false;
4706
- traverse4__default.default(
5052
+ traverse5__default.default(
4707
5053
  body,
4708
5054
  {
4709
5055
  noScope: true,
@@ -4755,7 +5101,7 @@ function collectionItemNames(callback) {
4755
5101
  function collectionDisplayFields(callback, itemNames) {
4756
5102
  const fields = /* @__PURE__ */ new Set();
4757
5103
  if (!callback.body) return [];
4758
- traverse4__default.default(
5104
+ traverse5__default.default(
4759
5105
  callback.body,
4760
5106
  {
4761
5107
  noScope: true,
@@ -4772,7 +5118,7 @@ function collectionDisplayFields(callback, itemNames) {
4772
5118
  function collectionKeyField(callback, itemNames) {
4773
5119
  let keyField;
4774
5120
  if (!callback.body) return void 0;
4775
- traverse4__default.default(
5121
+ traverse5__default.default(
4776
5122
  callback.body,
4777
5123
  {
4778
5124
  noScope: true,
@@ -4915,7 +5261,7 @@ var WebNavigationAnalyzer = class {
4915
5261
  if (BabelTypes__namespace.isJSXElement(child)) visitRoute(child, fullPath);
4916
5262
  }
4917
5263
  };
4918
- traverse4__default.default(ast, {
5264
+ traverse5__default.default(ast, {
4919
5265
  JSXElement: (nodePath) => {
4920
5266
  const name = getJsxElementName(nodePath.node.openingElement);
4921
5267
  if (name !== "Routes" && name !== "Route") return;
@@ -4954,7 +5300,7 @@ var WebNavigationAnalyzer = class {
4954
5300
  "createMemoryRouter",
4955
5301
  "useRoutes"
4956
5302
  ]);
4957
- traverse4__default.default(ast, {
5303
+ traverse5__default.default(ast, {
4958
5304
  CallExpression: (nodePath) => {
4959
5305
  const callee = nodePath.node.callee;
4960
5306
  if (!BabelTypes__namespace.isIdentifier(callee) || !ROUTER_FACTORIES.has(callee.name)) return;
@@ -5387,8 +5733,8 @@ var ZodParsedType = util.arrayToEnum([
5387
5733
  "set"
5388
5734
  ]);
5389
5735
  var getParsedType = (data) => {
5390
- const t13 = typeof data;
5391
- switch (t13) {
5736
+ const t15 = typeof data;
5737
+ switch (t15) {
5392
5738
  case "undefined":
5393
5739
  return ZodParsedType.undefined;
5394
5740
  case "string":
@@ -9233,7 +9579,25 @@ function isSafeImageUrl(value) {
9233
9579
  return SAFE_IMAGE_URL_RE.test(trimmed);
9234
9580
  }
9235
9581
 
9236
- // ../shared/dist/chunk-NMQNNPSJ.mjs
9582
+ // ../shared/dist/chunk-KUMPS6RH.mjs
9583
+ var SUPPORTED_APPILOTS_LOCALES = ["pt-BR", "en", "es", "fr"];
9584
+
9585
+ // ../shared/dist/chunk-2GTRKNPJ.mjs
9586
+ var symbols = external_exports.array(external_exports.string().max(240)).max(12);
9587
+ var controlEvidenceSchema = external_exports.object({
9588
+ version: external_exports.literal(1),
9589
+ siteId: external_exports.string().regex(/^[a-f0-9]{20}$/),
9590
+ component: external_exports.string().max(240),
9591
+ icons: symbols,
9592
+ handler: external_exports.string().max(240).optional(),
9593
+ calls: symbols,
9594
+ argumentBindings: symbols,
9595
+ conditions: symbols,
9596
+ nativeConfirmation: external_exports.object({
9597
+ title: external_exports.string().max(240).optional(),
9598
+ destructiveOption: external_exports.boolean()
9599
+ }).optional()
9600
+ });
9237
9601
  var locatorSourceSchema = external_exports.enum([
9238
9602
  "appilotsId",
9239
9603
  "testID",
@@ -9660,7 +10024,9 @@ var snapshotInputSchema = external_exports.object({
9660
10024
  inModal: external_exports.boolean().optional()
9661
10025
  }).passthrough();
9662
10026
  var snapshotButtonSchema = external_exports.object({
10027
+ controlEvidence: controlEvidenceSchema.optional(),
9663
10028
  id: boundedString(160).optional(),
10029
+ dispatchable: external_exports.boolean().optional(),
9664
10030
  provenance: identityProvenanceSchema.optional(),
9665
10031
  /**
9666
10032
  * False when the control is mounted but currently OUTSIDE the window —
@@ -9721,7 +10087,36 @@ var snapshotListSchema = external_exports.object({
9721
10087
  */
9722
10088
  source: boundedString(40).optional(),
9723
10089
  itemCount: external_exports.number().int().optional(),
10090
+ /**
10091
+ * Size of the whole collection when the app declared it, for a list
10092
+ * that is a WINDOW onto more data than it holds.
10093
+ *
10094
+ * `itemCount` is how many rows the list is rendering from and
10095
+ * `visibleItemCount` how many of those are mounted; neither can
10096
+ * express "there are 36 and you are looking at the first 20",
10097
+ * because a paginated list's `data` is the page. Absent means
10098
+ * unknown — never "same as itemCount".
10099
+ */
10100
+ totalItemCount: external_exports.number().int().optional(),
9724
10101
  visibleItemCount: external_exports.number().int().optional(),
10102
+ viewportItemCount: external_exports.number().int().nonnegative().optional(),
10103
+ exploration: external_exports.object({
10104
+ revision: external_exports.number().int().nonnegative(),
10105
+ observedItemCount: external_exports.number().int().min(0).max(5e3),
10106
+ observedRanges: external_exports.array(
10107
+ external_exports.object({
10108
+ start: external_exports.number().int().nonnegative(),
10109
+ end: external_exports.number().int().nonnegative()
10110
+ })
10111
+ ).max(32),
10112
+ rangesTruncated: external_exports.boolean(),
10113
+ coverage: external_exports.enum(["partial", "all-loaded"]),
10114
+ pagination: external_exports.enum(["possible", "not-declared"]),
10115
+ scrollSteps: external_exports.number().int().nonnegative(),
10116
+ remainingScrollSteps: external_exports.number().int().nonnegative(),
10117
+ consecutiveNoProgress: external_exports.number().int().nonnegative(),
10118
+ lastScroll: external_exports.enum(["moved", "no-progress", "boundary", "unverified"]).optional()
10119
+ }).optional(),
9725
10120
  refreshing: external_exports.boolean().optional(),
9726
10121
  empty: external_exports.boolean().optional(),
9727
10122
  label: boundedString(300).optional(),
@@ -9769,6 +10164,7 @@ var snapshotChoiceGroupSchema = external_exports.object({
9769
10164
  }).passthrough();
9770
10165
  var snapshotElementSchema = external_exports.object({
9771
10166
  id: boundedString(160).optional(),
10167
+ dispatchable: external_exports.boolean().optional(),
9772
10168
  role: boundedString(40).optional(),
9773
10169
  label: boundedString(300).optional(),
9774
10170
  texts: external_exports.array(boundedString(500)).max(50).optional(),
@@ -9831,6 +10227,8 @@ var agentSnapshotSchema = external_exports.object({
9831
10227
  loadingFinished: external_exports.boolean().optional(),
9832
10228
  textsAdded: external_exports.number().int().nonnegative().optional(),
9833
10229
  textsRemoved: external_exports.number().int().nonnegative().optional(),
10230
+ buttonsAddedIndices: external_exports.array(external_exports.number().int().nonnegative()).max(12).optional(),
10231
+ buttonsRemoved: external_exports.number().int().nonnegative().optional(),
9834
10232
  visibleRowsDelta: external_exports.number().int().optional(),
9835
10233
  totalRowsDelta: external_exports.number().int().optional(),
9836
10234
  fieldsNewlyFilled: external_exports.array(boundedString(160)).max(12).optional(),
@@ -9856,6 +10254,10 @@ var agentSnapshotSchema = external_exports.object({
9856
10254
  }).passthrough().optional()
9857
10255
  }).passthrough();
9858
10256
  var agentContextSchema = external_exports.object({
10257
+ missionProtocol: external_exports.literal(1).optional(),
10258
+ missionId: boundedString(160).optional(),
10259
+ /** Preferred supported device language, reported by the SDK independently of map/UI labels. */
10260
+ deviceLocale: external_exports.enum(SUPPORTED_APPILOTS_LOCALES).optional(),
9859
10261
  /**
9860
10262
  * Client platform this observation was captured on. Optional and
9861
10263
  * additive (see `clientPlatformSchema`) — absent means
@@ -10106,6 +10508,12 @@ var actionDiagnoseSchema = external_exports.object({
10106
10508
  requiresUserInput: external_exports.boolean().optional()
10107
10509
  });
10108
10510
  var actionResultSchema = external_exports.object({
10511
+ nativeConfirmation: external_exports.object({
10512
+ title: external_exports.string().max(300),
10513
+ message: external_exports.string().max(1e3).optional(),
10514
+ buttonLabel: external_exports.string().max(160).optional(),
10515
+ handlerCompleted: external_exports.boolean()
10516
+ }).optional(),
10109
10517
  actionId: external_exports.string(),
10110
10518
  type: external_exports.string(),
10111
10519
  success: external_exports.boolean(),
@@ -10233,7 +10641,7 @@ external_exports.object({
10233
10641
  /** null = remove webhook, undefined = leave unchanged. */
10234
10642
  budgetWebhookUrl: external_exports.string().url().nullable().optional()
10235
10643
  }).strict();
10236
- var localeSchema = external_exports.enum(["pt-BR", "en", "es"]);
10644
+ var localeSchema = external_exports.enum(SUPPORTED_APPILOTS_LOCALES);
10237
10645
  var hexColorSchema = external_exports.string().regex(/^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/, {
10238
10646
  message: "Must be a hex color like #6366f1"
10239
10647
  });
@@ -11244,9 +11652,22 @@ var MCPGenerator = class _MCPGenerator {
11244
11652
  const filePath = path2__namespace.default.resolve(outputDir, `mcp-document.${this.options.format}`);
11245
11653
  await fs.writeFile(filePath, serialized, "utf-8");
11246
11654
  console.log(`[MCPGenerator] Document written to: ${filePath}`);
11655
+ const controlFiles = analyzed.controlEvidenceFiles ?? {};
11656
+ await fs.writeFile(
11657
+ path2__namespace.default.resolve(outputDir, "control-evidence.json"),
11658
+ JSON.stringify({ version: 1, files: controlFiles }, null, 2),
11659
+ "utf-8"
11660
+ );
11247
11661
  const checksumFilePath = path2__namespace.default.resolve(outputDir, ".appilots-checksum");
11248
11662
  await fs.writeFile(checksumFilePath, checksum, "utf-8");
11249
11663
  console.log(`[MCPGenerator] Checksum written to: ${checksumFilePath}`);
11664
+ const evidenceCount = Object.values(controlFiles).reduce(
11665
+ (sum, entries) => sum + entries.length,
11666
+ 0
11667
+ );
11668
+ console.log(
11669
+ `[MCPGenerator] Source evidence: ${evidenceCount} icon controls across ${Object.keys(controlFiles).length} files (runtime binding required)`
11670
+ );
11250
11671
  console.log("[MCPGenerator] Generation complete!");
11251
11672
  return {
11252
11673
  document,
@@ -11312,7 +11733,8 @@ var KNOWN_CONFIG_KEYS = [
11312
11733
  "navigationExclude",
11313
11734
  "platform",
11314
11735
  "manifestPath",
11315
- "eval"
11736
+ "eval",
11737
+ "knowledge"
11316
11738
  ];
11317
11739
  var KEY_ALIASES = {
11318
11740
  apiUrl: "serverUrl",
@@ -11460,7 +11882,8 @@ function saveConfig(dir, config) {
11460
11882
  navigationExclude: config.navigationExclude || existingConfig?.navigationExclude,
11461
11883
  platform: config.platform || existingConfig?.platform,
11462
11884
  manifestPath: config.manifestPath || existingConfig?.manifestPath,
11463
- eval: config.eval || existingConfig?.eval
11885
+ eval: config.eval || existingConfig?.eval,
11886
+ knowledge: config.knowledge || existingConfig?.knowledge
11464
11887
  };
11465
11888
  try {
11466
11889
  fs$1.writeFileSync(configPath, JSON.stringify(mergedConfig, null, 2), "utf-8");
@@ -11553,6 +11976,19 @@ function validateConfig(config) {
11553
11976
  if (config.manifestPath !== void 0 && typeof config.manifestPath !== "string") {
11554
11977
  errors.push("manifestPath must be a string");
11555
11978
  }
11979
+ if (config.knowledge !== void 0) {
11980
+ if (typeof config.knowledge !== "object" || config.knowledge === null || Array.isArray(config.knowledge)) {
11981
+ errors.push("knowledge must be an object");
11982
+ } else {
11983
+ const kc = config.knowledge;
11984
+ for (const field of ["sources", "exclude"]) {
11985
+ const value = kc[field];
11986
+ if (value !== void 0 && (!Array.isArray(value) || !value.every((item) => typeof item === "string"))) {
11987
+ errors.push(`knowledge.${field} must be an array of strings`);
11988
+ }
11989
+ }
11990
+ }
11991
+ }
11556
11992
  if (config.eval !== void 0) {
11557
11993
  if (typeof config.eval !== "object" || config.eval === null || Array.isArray(config.eval)) {
11558
11994
  errors.push("eval must be an object");
@@ -11829,6 +12265,48 @@ var AppilotsAPIClient = class {
11829
12265
  };
11830
12266
  }
11831
12267
  }
12268
+ /**
12269
+ * Syncs knowledge documents with the Appilots backend.
12270
+ *
12271
+ * @param documents Array of documents to sync (filename, base64 content, mimeType, checksum)
12272
+ * @returns KnowledgeSyncResult with counts of uploaded, skipped, and errored docs
12273
+ */
12274
+ async knowledgeSync(documents) {
12275
+ try {
12276
+ const response = await this.request(`${this.baseUrl}/cli/knowledge/sync`, {
12277
+ method: "POST",
12278
+ headers: {
12279
+ "Content-Type": "application/json",
12280
+ Authorization: `Bearer ${this.apiKey}`
12281
+ },
12282
+ body: JSON.stringify({ documents })
12283
+ });
12284
+ if (!response.ok) {
12285
+ const errorData = await response.json().catch(() => ({}));
12286
+ return {
12287
+ success: false,
12288
+ uploaded: 0,
12289
+ skipped: 0,
12290
+ errors: 0,
12291
+ error: describeApiError(errorData, `HTTP ${response.status}: ${response.statusText}`)
12292
+ };
12293
+ }
12294
+ const json = await response.json();
12295
+ const inner = json.data ?? json;
12296
+ return {
12297
+ success: true,
12298
+ ...inner
12299
+ };
12300
+ } catch (error) {
12301
+ return {
12302
+ success: false,
12303
+ uploaded: 0,
12304
+ skipped: 0,
12305
+ errors: 0,
12306
+ error: error instanceof Error ? error.message : "Failed to sync knowledge with Appilots API"
12307
+ };
12308
+ }
12309
+ }
11832
12310
  /**
11833
12311
  * Checks if the Appilots API server is healthy
11834
12312
  *
@@ -11847,7 +12325,7 @@ var AppilotsAPIClient = class {
11847
12325
  };
11848
12326
 
11849
12327
  // src/version.ts
11850
- var CLI_VERSION = "0.11.3";
12328
+ var CLI_VERSION = "0.13.0";
11851
12329
 
11852
12330
  exports.AppilotsAPIClient = AppilotsAPIClient;
11853
12331
  exports.CLI_VERSION = CLI_VERSION;