@appilots/cli 0.11.3 → 0.14.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;
@@ -417,6 +754,9 @@ function sanitisePermissionsObject(obj) {
417
754
  if (typeof obj.isPii === "boolean") {
418
755
  out.isPii = obj.isPii;
419
756
  }
757
+ if (typeof obj.assessment === "boolean") {
758
+ out.assessment = obj.assessment;
759
+ }
420
760
  if (Array.isArray(obj.blockedActions)) {
421
761
  const ids = obj.blockedActions.filter((x) => typeof x === "string" && !!x);
422
762
  if (ids.length > 0) out.blockedActions = ids;
@@ -444,6 +784,7 @@ var ScreenAnalyzer = class {
444
784
  routeTargetFiles;
445
785
  /** §D: Count of screens filtered out in strict mode (available after analyze()) */
446
786
  screensFilteredOut = 0;
787
+ controlEvidenceFiles = {};
447
788
  constructor(config, options) {
448
789
  this.config = config;
449
790
  this.routeTargetFiles = options?.routeTargetFiles ?? /* @__PURE__ */ new Set();
@@ -544,6 +885,11 @@ var ScreenAnalyzer = class {
544
885
  title: registerScreenMeta?.title,
545
886
  description: registerScreenMeta?.description,
546
887
  components,
888
+ controlCandidates: extractControlEvidence(
889
+ ast,
890
+ source,
891
+ path2__namespace.default.relative(this.config.rootDir, filePath)
892
+ ),
547
893
  forms,
548
894
  actions,
549
895
  navigationTargets,
@@ -554,6 +900,8 @@ var ScreenAnalyzer = class {
554
900
  ...permissionsFromJsDoc.isPii ? { isPii: true } : {}
555
901
  } : {}
556
902
  };
903
+ if (descriptor.controlCandidates?.length)
904
+ this.controlEvidenceFiles[path2__namespace.default.relative(this.config.rootDir, filePath).split(path2__namespace.default.sep).join("/")] = descriptor.controlCandidates;
557
905
  descriptor.__hasRegisterScreen = hasRegisterScreenCall;
558
906
  return descriptor;
559
907
  }
@@ -563,7 +911,7 @@ var ScreenAnalyzer = class {
563
911
  */
564
912
  detectRegisterScreenCall(ast) {
565
913
  let found = false;
566
- traverse4__default.default(ast, {
914
+ traverse5__default.default(ast, {
567
915
  CallExpression: (nodePath) => {
568
916
  if (found) return;
569
917
  const callee = nodePath.node.callee;
@@ -580,7 +928,7 @@ var ScreenAnalyzer = class {
580
928
  */
581
929
  extractRegisterScreenMetadata(ast) {
582
930
  let metadata = null;
583
- traverse4__default.default(ast, {
931
+ traverse5__default.default(ast, {
584
932
  CallExpression: (nodePath) => {
585
933
  const callee = nodePath.node.callee;
586
934
  if (BabelTypes__namespace.isIdentifier(callee) && callee.name === "registerScreen" || BabelTypes__namespace.isMemberExpression(callee) && BabelTypes__namespace.isIdentifier(callee.property) && callee.property.name === "registerScreen") {
@@ -877,7 +1225,7 @@ var ScreenAnalyzer = class {
877
1225
  */
878
1226
  extractDefaultComponentName(ast) {
879
1227
  let componentName = "";
880
- traverse4__default.default(ast, {
1228
+ traverse5__default.default(ast, {
881
1229
  ExportDefaultDeclaration: (nodePath) => {
882
1230
  const declaration = nodePath.node.declaration;
883
1231
  if (BabelTypes__namespace.isFunctionDeclaration(declaration) && declaration.id?.name) {
@@ -902,7 +1250,7 @@ var ScreenAnalyzer = class {
902
1250
  */
903
1251
  extractNavigationTargets(ast) {
904
1252
  const targets = /* @__PURE__ */ new Set();
905
- traverse4__default.default(ast, {
1253
+ traverse5__default.default(ast, {
906
1254
  CallExpression: (nodePath) => {
907
1255
  const callee = nodePath.node.callee;
908
1256
  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 +1269,7 @@ var ScreenAnalyzer = class {
921
1269
  extractForms(ast) {
922
1270
  const forms = [];
923
1271
  const fields = /* @__PURE__ */ new Map();
924
- traverse4__default.default(ast, {
1272
+ traverse5__default.default(ast, {
925
1273
  JSXOpeningElement: (nodePath) => {
926
1274
  const element = nodePath.node;
927
1275
  if (BabelTypes__namespace.isJSXIdentifier(element.name)) {
@@ -1042,7 +1390,7 @@ var ScreenAnalyzer = class {
1042
1390
  extractComponents(ast) {
1043
1391
  const components = [];
1044
1392
  const seen = /* @__PURE__ */ new Set();
1045
- traverse4__default.default(ast, {
1393
+ traverse5__default.default(ast, {
1046
1394
  JSXOpeningElement: (nodePath) => {
1047
1395
  const element = nodePath.node;
1048
1396
  if (BabelTypes__namespace.isJSXIdentifier(element.name)) {
@@ -1106,7 +1454,7 @@ var ScreenAnalyzer = class {
1106
1454
  const actionLabels = new Map(
1107
1455
  actions.filter((a) => a.label).map((a) => [this.normalizeLabel(a.label), a])
1108
1456
  );
1109
- traverse4__default.default(ast, {
1457
+ traverse5__default.default(ast, {
1110
1458
  JSXOpeningElement: (nodePath) => {
1111
1459
  const element = nodePath.node;
1112
1460
  if (BabelTypes__namespace.isJSXIdentifier(element.name)) {
@@ -1175,7 +1523,7 @@ var ScreenAnalyzer = class {
1175
1523
  }
1176
1524
  collectButtonHandlersByLabel(ast) {
1177
1525
  const out = /* @__PURE__ */ new Map();
1178
- traverse4__default.default(ast, {
1526
+ traverse5__default.default(ast, {
1179
1527
  JSXOpeningElement: (nodePath) => {
1180
1528
  const element = nodePath.node;
1181
1529
  if (!BabelTypes__namespace.isJSXIdentifier(element.name)) return;
@@ -1257,7 +1605,7 @@ var ScreenAnalyzer = class {
1257
1605
  }
1258
1606
  };
1259
1607
  if (fn.body) {
1260
- traverse4__default.default(
1608
+ traverse5__default.default(
1261
1609
  fn.body,
1262
1610
  {
1263
1611
  noScope: true,
@@ -1454,7 +1802,7 @@ var ScreenAnalyzer = class {
1454
1802
  extractCollections(ast) {
1455
1803
  const renderItemFns = this.collectRenderItemFunctions(ast);
1456
1804
  const collections = [];
1457
- traverse4__default.default(ast, {
1805
+ traverse5__default.default(ast, {
1458
1806
  JSXOpeningElement: (nodePath) => {
1459
1807
  const element = nodePath.node;
1460
1808
  if (!BabelTypes__namespace.isJSXIdentifier(element.name)) return;
@@ -1491,7 +1839,7 @@ var ScreenAnalyzer = class {
1491
1839
  }
1492
1840
  collectRenderItemFunctions(ast) {
1493
1841
  const out = /* @__PURE__ */ new Map();
1494
- traverse4__default.default(ast, {
1842
+ traverse5__default.default(ast, {
1495
1843
  VariableDeclarator: (nodePath) => {
1496
1844
  if (!BabelTypes__namespace.isIdentifier(nodePath.node.id)) return;
1497
1845
  const init = nodePath.node.init;
@@ -1534,7 +1882,7 @@ var ScreenAnalyzer = class {
1534
1882
  }
1535
1883
  extractRowAction(fn) {
1536
1884
  let action;
1537
- traverse4__default.default(
1885
+ traverse5__default.default(
1538
1886
  fn.body,
1539
1887
  {
1540
1888
  noScope: true,
@@ -1587,7 +1935,7 @@ var ScreenAnalyzer = class {
1587
1935
  } else if (BabelTypes__namespace.isIdentifier(firstParam)) {
1588
1936
  itemNames.add(firstParam.name);
1589
1937
  }
1590
- traverse4__default.default(
1938
+ traverse5__default.default(
1591
1939
  fn.body,
1592
1940
  {
1593
1941
  noScope: true,
@@ -1629,7 +1977,7 @@ var ScreenAnalyzer = class {
1629
1977
  inferSearchField(ast, dataSource) {
1630
1978
  if (!dataSource) return void 0;
1631
1979
  let queryBinding;
1632
- traverse4__default.default(ast, {
1980
+ traverse5__default.default(ast, {
1633
1981
  CallExpression: (nodePath) => {
1634
1982
  const node = nodePath.node;
1635
1983
  if (!BabelTypes__namespace.isMemberExpression(node.callee)) return;
@@ -1640,7 +1988,7 @@ var ScreenAnalyzer = class {
1640
1988
  const fn = node.arguments[0];
1641
1989
  if (!BabelTypes__namespace.isArrowFunctionExpression(fn) && !BabelTypes__namespace.isFunctionExpression(fn))
1642
1990
  return;
1643
- traverse4__default.default(
1991
+ traverse5__default.default(
1644
1992
  fn.body,
1645
1993
  {
1646
1994
  noScope: true,
@@ -1682,7 +2030,7 @@ var ScreenAnalyzer = class {
1682
2030
  }
1683
2031
  };
1684
2032
  var EXTENSIONS = [".tsx", ".ts", ".jsx", ".js", ".mjs", ".cjs"];
1685
- var MAX_HOPS = 8;
2033
+ var MAX_HOPS2 = 8;
1686
2034
  var ModuleGraph = class {
1687
2035
  asts = /* @__PURE__ */ new Map();
1688
2036
  resolved = /* @__PURE__ */ new Map();
@@ -1801,7 +2149,7 @@ var ModuleGraph = class {
1801
2149
  * Devolve o arquivo e o nome sob o qual ele é definido lá.
1802
2150
  */
1803
2151
  resolveBinding(file, name, hops = 0) {
1804
- if (hops > MAX_HOPS) return null;
2152
+ if (hops > MAX_HOPS2) return null;
1805
2153
  if (this.topLevelInit(file, name) !== null) return { file, name };
1806
2154
  const binding = this.imports(file).get(name);
1807
2155
  if (binding) {
@@ -1855,7 +2203,7 @@ var ModuleGraph = class {
1855
2203
  * com interpolação nem valor calculado, de propósito.
1856
2204
  */
1857
2205
  stringConstant(file, node, hops = 0) {
1858
- if (!node || hops > MAX_HOPS) return null;
2206
+ if (!node || hops > MAX_HOPS2) return null;
1859
2207
  if (BabelTypes__namespace.isStringLiteral(node)) return node.value;
1860
2208
  if (BabelTypes__namespace.isTemplateLiteral(node)) {
1861
2209
  return node.expressions.length === 0 ? node.quasis[0]?.value.cooked ?? null : null;
@@ -1884,7 +2232,7 @@ var ModuleGraph = class {
1884
2232
  * que é como o `pocketpal` monta todas as telas do Drawer).
1885
2233
  */
1886
2234
  componentFile(file, node, hops = 0) {
1887
- if (!node || hops > MAX_HOPS) return null;
2235
+ if (!node || hops > MAX_HOPS2) return null;
1888
2236
  if (BabelTypes__namespace.isCallExpression(node)) {
1889
2237
  for (const arg of node.arguments) {
1890
2238
  if (BabelTypes__namespace.isIdentifier(arg) || BabelTypes__namespace.isMemberExpression(arg)) {
@@ -2080,7 +2428,7 @@ function workspaceGlobs(dir) {
2080
2428
  }
2081
2429
  function expandWorkspaceGlobs(root, globs) {
2082
2430
  const out = [];
2083
- const add = (dir) => {
2431
+ const add2 = (dir) => {
2084
2432
  const pkgPath = path2__namespace.default.join(dir, "package.json");
2085
2433
  if (!fs$1.existsSync(pkgPath)) return;
2086
2434
  try {
@@ -2101,12 +2449,12 @@ function expandWorkspaceGlobs(root, globs) {
2101
2449
  for (const entry of entries) {
2102
2450
  const dir = path2__namespace.default.join(parent, entry);
2103
2451
  try {
2104
- if (fs$1.statSync(dir).isDirectory()) add(dir);
2452
+ if (fs$1.statSync(dir).isDirectory()) add2(dir);
2105
2453
  } catch {
2106
2454
  }
2107
2455
  }
2108
2456
  } else if (!glob.includes("*")) {
2109
- add(path2__namespace.default.join(root, glob));
2457
+ add2(path2__namespace.default.join(root, glob));
2110
2458
  }
2111
2459
  }
2112
2460
  return out.sort((a, b) => b.name.length - a.name.length);
@@ -2426,7 +2774,7 @@ var NavigationAnalyzer = class {
2426
2774
  const ast = this.graph.parse(filePath);
2427
2775
  if (!ast) return [];
2428
2776
  const out = /* @__PURE__ */ new Map();
2429
- traverse4__default.default(ast, {
2777
+ traverse5__default.default(ast, {
2430
2778
  CallExpression: (nodePath) => {
2431
2779
  const callee = nodePath.node.callee;
2432
2780
  if (!BabelTypes__namespace.isIdentifier(callee)) return;
@@ -2587,7 +2935,7 @@ var NavigationAnalyzer = class {
2587
2935
  const ast = this.graph.parse(filePath);
2588
2936
  if (!ast) return [];
2589
2937
  const found = /* @__PURE__ */ new Map();
2590
- traverse4__default.default(ast, {
2938
+ traverse5__default.default(ast, {
2591
2939
  JSXElement: (nodePath) => {
2592
2940
  const { node } = nodePath;
2593
2941
  const openingElement = node.openingElement;
@@ -2659,7 +3007,7 @@ var NavigationAnalyzer = class {
2659
3007
  if (!ast) return [];
2660
3008
  const byNavigator = /* @__PURE__ */ new Map();
2661
3009
  const resolved = /* @__PURE__ */ new Map();
2662
- traverse4__default.default(ast, {
3010
+ traverse5__default.default(ast, {
2663
3011
  JSXElement: (nodePath) => {
2664
3012
  const name = nodePath.node.openingElement.name;
2665
3013
  if (!BabelTypes__namespace.isJSXMemberExpression(name) || !BabelTypes__namespace.isJSXIdentifier(name.object)) return;
@@ -2892,7 +3240,7 @@ var NavigationAnalyzer = class {
2892
3240
  ...this.config.parserPlugins || []
2893
3241
  ]
2894
3242
  });
2895
- traverse4__default.default(ast, {
3243
+ traverse5__default.default(ast, {
2896
3244
  TSTypeAliasDeclaration: (nodePath) => {
2897
3245
  const { node } = nodePath;
2898
3246
  const typeName = node.id.name;
@@ -2965,7 +3313,7 @@ var NavigationAnalyzer = class {
2965
3313
  if (type.type === "TSUndefinedKeyword") return "undefined";
2966
3314
  if (type.type === "TSNullKeyword") return "null";
2967
3315
  if (type.type === "TSUnionType") {
2968
- return type.types.map((t13) => this.typeToString(t13)).join(" | ");
3316
+ return type.types.map((t15) => this.typeToString(t15)).join(" | ");
2969
3317
  }
2970
3318
  if (type.type === "TSTypeLiteral") {
2971
3319
  return "object";
@@ -2986,7 +3334,7 @@ var NavigationAnalyzer = class {
2986
3334
  /** Attach parsed type params to navigator screens */
2987
3335
  attachParamsToNavigators(navigators, types) {
2988
3336
  for (const navigator of navigators) {
2989
- const matchingType = types.find((t13) => t13.type === navigator.type);
3337
+ const matchingType = types.find((t15) => t15.type === navigator.type);
2990
3338
  if (matchingType) {
2991
3339
  for (const screen of navigator.screens) {
2992
3340
  const screenParams = matchingType.paramEntries.get(screen.name);
@@ -3075,7 +3423,7 @@ var ComponentAnalyzer = class {
3075
3423
  plugins: ["jsx", "typescript", ["decorators", { decoratorsBeforeExport: true }]]
3076
3424
  });
3077
3425
  const components = [];
3078
- traverse4__default.default(ast, {
3426
+ traverse5__default.default(ast, {
3079
3427
  JSXElement: (path11) => {
3080
3428
  const component = this.extractComponentFromJSXElement(path11.node);
3081
3429
  if (component) {
@@ -3203,12 +3551,12 @@ var FormAnalyzer = class {
3203
3551
  this.stateVariables.clear();
3204
3552
  this.inputElements = [];
3205
3553
  this.submitButtons = [];
3206
- traverse4__default.default(ast, {
3554
+ traverse5__default.default(ast, {
3207
3555
  CallExpression: (path11) => {
3208
3556
  this.extractStateVariables(path11.node);
3209
3557
  }
3210
3558
  });
3211
- traverse4__default.default(ast, {
3559
+ traverse5__default.default(ast, {
3212
3560
  JSXElement: (path11) => {
3213
3561
  this.extractFormElements(path11.node);
3214
3562
  }
@@ -3244,23 +3592,23 @@ var FormAnalyzer = class {
3244
3592
  for (const attr of openingElement.attributes) {
3245
3593
  if (BabelTypes__namespace.isJSXAttribute(attr) && BabelTypes__namespace.isJSXIdentifier(attr.name)) {
3246
3594
  const propName = attr.name.name;
3247
- const propValue = this.extractAttributeValue(attr.value);
3595
+ const propValue2 = this.extractAttributeValue(attr.value);
3248
3596
  switch (propName) {
3249
3597
  case "label":
3250
- info.label = propValue;
3598
+ info.label = propValue2;
3251
3599
  break;
3252
3600
  case "placeholder":
3253
- info.placeholder = propValue;
3601
+ info.placeholder = propValue2;
3254
3602
  break;
3255
3603
  case "keyboardType":
3256
- info.keyboardType = propValue;
3604
+ info.keyboardType = propValue2;
3257
3605
  break;
3258
3606
  case "testID":
3259
- info.testID = propValue;
3607
+ info.testID = propValue2;
3260
3608
  break;
3261
3609
  case "appilotsId":
3262
- info.appilotsId = propValue;
3263
- if (propValue) info.varName = propValue;
3610
+ info.appilotsId = propValue2;
3611
+ if (propValue2) info.varName = propValue2;
3264
3612
  break;
3265
3613
  case "value":
3266
3614
  if (attr.value && BabelTypes__namespace.isJSXExpressionContainer(attr.value) && BabelTypes__namespace.isIdentifier(attr.value.expression)) {
@@ -3309,7 +3657,7 @@ var FormAnalyzer = class {
3309
3657
  }
3310
3658
  extractValidationRules(ast) {
3311
3659
  const rules = {};
3312
- traverse4__default.default(ast, {
3660
+ traverse5__default.default(ast, {
3313
3661
  IfStatement: (path11) => {
3314
3662
  const test = path11.node.test;
3315
3663
  const rule = this.extractRuleFromCondition(test);
@@ -3463,7 +3811,7 @@ async function composeAffordances(options) {
3463
3811
  const exclusive = children.filter((c) => (fanIn.get(c) ?? 0) <= MAX_FAN_IN);
3464
3812
  if (exclusive.length === 0) continue;
3465
3813
  const actionIds = new Set(screen.actions.map((a) => a.id));
3466
- const targetIds = new Set((screen.targets ?? []).map((t13) => t13.id));
3814
+ const targetIds = new Set((screen.targets ?? []).map((t15) => t15.id));
3467
3815
  const formIds = new Set(screen.forms.map((f) => f.id));
3468
3816
  let gained = false;
3469
3817
  for (const file of exclusive) {
@@ -3605,6 +3953,7 @@ var ReactNativePlatformAnalyzer = class {
3605
3953
  });
3606
3954
  return {
3607
3955
  screens: enrichedScreens,
3956
+ controlEvidenceFiles: screenAnalyzer.controlEvidenceFiles,
3608
3957
  navigation,
3609
3958
  analyzedFiles: screenFiles.length,
3610
3959
  ...screenAnalyzer.screensFilteredOut > 0 ? { screensFilteredOut: screenAnalyzer.screensFilteredOut } : {},
@@ -3869,7 +4218,7 @@ function extractWebNavigationCalls(ast) {
3869
4218
  calls.push({ method: "navigate", targetPath: node.right.value });
3870
4219
  }
3871
4220
  };
3872
- traverse4__default.default(ast, {
4221
+ traverse5__default.default(ast, {
3873
4222
  noScope: !BabelTypes__namespace.isFile(ast),
3874
4223
  enter: (nodePath) => inspect(nodePath.node)
3875
4224
  });
@@ -3991,7 +4340,7 @@ var WebScreenAnalyzer = class {
3991
4340
  // ── registerScreen ────────────────────────────────────────────────
3992
4341
  detectRegisterScreenCall(ast) {
3993
4342
  let found = false;
3994
- traverse4__default.default(ast, {
4343
+ traverse5__default.default(ast, {
3995
4344
  CallExpression: (nodePath) => {
3996
4345
  if (found) return;
3997
4346
  if (isRegisterScreenCallee(nodePath.node.callee)) {
@@ -4004,7 +4353,7 @@ var WebScreenAnalyzer = class {
4004
4353
  }
4005
4354
  extractRegisterScreenMetadata(ast) {
4006
4355
  let plain = null;
4007
- traverse4__default.default(ast, {
4356
+ traverse5__default.default(ast, {
4008
4357
  CallExpression: (nodePath) => {
4009
4358
  if (!isRegisterScreenCallee(nodePath.node.callee)) return;
4010
4359
  const arg = nodePath.node.arguments[0];
@@ -4052,7 +4401,7 @@ var WebScreenAnalyzer = class {
4052
4401
  extractComponentName(ast) {
4053
4402
  let defaultName = "";
4054
4403
  let firstExported = "";
4055
- traverse4__default.default(ast, {
4404
+ traverse5__default.default(ast, {
4056
4405
  ExportDefaultDeclaration: (nodePath) => {
4057
4406
  const declaration = nodePath.node.declaration;
4058
4407
  if (BabelTypes__namespace.isFunctionDeclaration(declaration) && declaration.id?.name) {
@@ -4081,7 +4430,7 @@ var WebScreenAnalyzer = class {
4081
4430
  extractComponents(ast) {
4082
4431
  const components = [];
4083
4432
  const seen = /* @__PURE__ */ new Set();
4084
- traverse4__default.default(ast, {
4433
+ traverse5__default.default(ast, {
4085
4434
  JSXOpeningElement: (nodePath) => {
4086
4435
  const element = nodePath.node;
4087
4436
  const name = getJsxElementName(element);
@@ -4102,7 +4451,7 @@ var WebScreenAnalyzer = class {
4102
4451
  // ── <label htmlFor> association ───────────────────────────────────
4103
4452
  collectHtmlForLabels(ast) {
4104
4453
  const labels = /* @__PURE__ */ new Map();
4105
- traverse4__default.default(ast, {
4454
+ traverse5__default.default(ast, {
4106
4455
  JSXElement: (nodePath) => {
4107
4456
  const element = nodePath.node;
4108
4457
  if (getJsxElementName(element.openingElement) !== "label") return;
@@ -4145,7 +4494,7 @@ var WebScreenAnalyzer = class {
4145
4494
  formBuckets.set(formElement, bucket);
4146
4495
  return bucket;
4147
4496
  };
4148
- traverse4__default.default(ast, {
4497
+ traverse5__default.default(ast, {
4149
4498
  JSXElement: (nodePath) => {
4150
4499
  const element = nodePath.node;
4151
4500
  const name = getJsxElementName(element.openingElement);
@@ -4162,7 +4511,7 @@ var WebScreenAnalyzer = class {
4162
4511
  if (!bucket.fields.has(field.name)) bucket.fields.set(field.name, field);
4163
4512
  }
4164
4513
  });
4165
- traverse4__default.default(ast, {
4514
+ traverse5__default.default(ast, {
4166
4515
  JSXElement: (nodePath) => {
4167
4516
  const element = nodePath.node;
4168
4517
  const name = getJsxElementName(element.openingElement);
@@ -4340,7 +4689,7 @@ var WebScreenAnalyzer = class {
4340
4689
  const actionLabels = new Map(
4341
4690
  actions.filter((a) => a.label).map((a) => [normalizeLabel(a.label), a])
4342
4691
  );
4343
- traverse4__default.default(ast, {
4692
+ traverse5__default.default(ast, {
4344
4693
  JSXElement: (nodePath) => {
4345
4694
  const element = nodePath.node;
4346
4695
  const name = getJsxElementName(element.openingElement);
@@ -4571,7 +4920,7 @@ var WebScreenAnalyzer = class {
4571
4920
  for (const call of extractWebNavigationCalls(ast)) {
4572
4921
  if (call.targetPath && call.targetPath.startsWith("/")) targets.add(call.targetPath);
4573
4922
  }
4574
- traverse4__default.default(ast, {
4923
+ traverse5__default.default(ast, {
4575
4924
  JSXOpeningElement: (nodePath) => {
4576
4925
  const element = nodePath.node;
4577
4926
  const name = getJsxElementName(element);
@@ -4591,7 +4940,7 @@ var WebScreenAnalyzer = class {
4591
4940
  extractCollections(ast) {
4592
4941
  const collections = [];
4593
4942
  const seen = /* @__PURE__ */ new Set();
4594
- traverse4__default.default(ast, {
4943
+ traverse5__default.default(ast, {
4595
4944
  JSXExpressionContainer: (nodePath) => {
4596
4945
  const expr = nodePath.node.expression;
4597
4946
  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 +5052,7 @@ function firstCalledFunctionName(node) {
4703
5052
  }
4704
5053
  function containsWindowConfirm(body) {
4705
5054
  let found = false;
4706
- traverse4__default.default(
5055
+ traverse5__default.default(
4707
5056
  body,
4708
5057
  {
4709
5058
  noScope: true,
@@ -4755,7 +5104,7 @@ function collectionItemNames(callback) {
4755
5104
  function collectionDisplayFields(callback, itemNames) {
4756
5105
  const fields = /* @__PURE__ */ new Set();
4757
5106
  if (!callback.body) return [];
4758
- traverse4__default.default(
5107
+ traverse5__default.default(
4759
5108
  callback.body,
4760
5109
  {
4761
5110
  noScope: true,
@@ -4772,7 +5121,7 @@ function collectionDisplayFields(callback, itemNames) {
4772
5121
  function collectionKeyField(callback, itemNames) {
4773
5122
  let keyField;
4774
5123
  if (!callback.body) return void 0;
4775
- traverse4__default.default(
5124
+ traverse5__default.default(
4776
5125
  callback.body,
4777
5126
  {
4778
5127
  noScope: true,
@@ -4915,7 +5264,7 @@ var WebNavigationAnalyzer = class {
4915
5264
  if (BabelTypes__namespace.isJSXElement(child)) visitRoute(child, fullPath);
4916
5265
  }
4917
5266
  };
4918
- traverse4__default.default(ast, {
5267
+ traverse5__default.default(ast, {
4919
5268
  JSXElement: (nodePath) => {
4920
5269
  const name = getJsxElementName(nodePath.node.openingElement);
4921
5270
  if (name !== "Routes" && name !== "Route") return;
@@ -4954,7 +5303,7 @@ var WebNavigationAnalyzer = class {
4954
5303
  "createMemoryRouter",
4955
5304
  "useRoutes"
4956
5305
  ]);
4957
- traverse4__default.default(ast, {
5306
+ traverse5__default.default(ast, {
4958
5307
  CallExpression: (nodePath) => {
4959
5308
  const callee = nodePath.node.callee;
4960
5309
  if (!BabelTypes__namespace.isIdentifier(callee) || !ROUTER_FACTORIES.has(callee.name)) return;
@@ -5387,8 +5736,8 @@ var ZodParsedType = util.arrayToEnum([
5387
5736
  "set"
5388
5737
  ]);
5389
5738
  var getParsedType = (data) => {
5390
- const t13 = typeof data;
5391
- switch (t13) {
5739
+ const t15 = typeof data;
5740
+ switch (t15) {
5392
5741
  case "undefined":
5393
5742
  return ZodParsedType.undefined;
5394
5743
  case "string":
@@ -9233,7 +9582,25 @@ function isSafeImageUrl(value) {
9233
9582
  return SAFE_IMAGE_URL_RE.test(trimmed);
9234
9583
  }
9235
9584
 
9236
- // ../shared/dist/chunk-NMQNNPSJ.mjs
9585
+ // ../shared/dist/chunk-KUMPS6RH.mjs
9586
+ var SUPPORTED_APPILOTS_LOCALES = ["pt-BR", "en", "es", "fr"];
9587
+
9588
+ // ../shared/dist/chunk-4RYPNBBY.mjs
9589
+ var symbols = external_exports.array(external_exports.string().max(240)).max(12);
9590
+ var controlEvidenceSchema = external_exports.object({
9591
+ version: external_exports.literal(1),
9592
+ siteId: external_exports.string().regex(/^[a-f0-9]{20}$/),
9593
+ component: external_exports.string().max(240),
9594
+ icons: symbols,
9595
+ handler: external_exports.string().max(240).optional(),
9596
+ calls: symbols,
9597
+ argumentBindings: symbols,
9598
+ conditions: symbols,
9599
+ nativeConfirmation: external_exports.object({
9600
+ title: external_exports.string().max(240).optional(),
9601
+ destructiveOption: external_exports.boolean()
9602
+ }).optional()
9603
+ });
9237
9604
  var locatorSourceSchema = external_exports.enum([
9238
9605
  "appilotsId",
9239
9606
  "testID",
@@ -9435,7 +9802,9 @@ var screenPermissionDescriptorSchema = external_exports.object({
9435
9802
  agentAccess: external_exports.enum(["read", "write", "none"]).optional(),
9436
9803
  requiresConfirmation: external_exports.boolean().optional(),
9437
9804
  blockedActions: external_exports.array(external_exports.string()).optional(),
9438
- isPii: external_exports.boolean().optional()
9805
+ isPii: external_exports.boolean().optional(),
9806
+ /** Graded assessment — see `screenPermissionSchema.assessment`. */
9807
+ assessment: external_exports.boolean().optional()
9439
9808
  }).passthrough();
9440
9809
  var screenDescriptorSchema = external_exports.object({
9441
9810
  name: external_exports.string(),
@@ -9660,7 +10029,9 @@ var snapshotInputSchema = external_exports.object({
9660
10029
  inModal: external_exports.boolean().optional()
9661
10030
  }).passthrough();
9662
10031
  var snapshotButtonSchema = external_exports.object({
10032
+ controlEvidence: controlEvidenceSchema.optional(),
9663
10033
  id: boundedString(160).optional(),
10034
+ dispatchable: external_exports.boolean().optional(),
9664
10035
  provenance: identityProvenanceSchema.optional(),
9665
10036
  /**
9666
10037
  * False when the control is mounted but currently OUTSIDE the window —
@@ -9721,7 +10092,36 @@ var snapshotListSchema = external_exports.object({
9721
10092
  */
9722
10093
  source: boundedString(40).optional(),
9723
10094
  itemCount: external_exports.number().int().optional(),
10095
+ /**
10096
+ * Size of the whole collection when the app declared it, for a list
10097
+ * that is a WINDOW onto more data than it holds.
10098
+ *
10099
+ * `itemCount` is how many rows the list is rendering from and
10100
+ * `visibleItemCount` how many of those are mounted; neither can
10101
+ * express "there are 36 and you are looking at the first 20",
10102
+ * because a paginated list's `data` is the page. Absent means
10103
+ * unknown — never "same as itemCount".
10104
+ */
10105
+ totalItemCount: external_exports.number().int().optional(),
9724
10106
  visibleItemCount: external_exports.number().int().optional(),
10107
+ viewportItemCount: external_exports.number().int().nonnegative().optional(),
10108
+ exploration: external_exports.object({
10109
+ revision: external_exports.number().int().nonnegative(),
10110
+ observedItemCount: external_exports.number().int().min(0).max(5e3),
10111
+ observedRanges: external_exports.array(
10112
+ external_exports.object({
10113
+ start: external_exports.number().int().nonnegative(),
10114
+ end: external_exports.number().int().nonnegative()
10115
+ })
10116
+ ).max(32),
10117
+ rangesTruncated: external_exports.boolean(),
10118
+ coverage: external_exports.enum(["partial", "all-loaded"]),
10119
+ pagination: external_exports.enum(["possible", "not-declared"]),
10120
+ scrollSteps: external_exports.number().int().nonnegative(),
10121
+ remainingScrollSteps: external_exports.number().int().nonnegative(),
10122
+ consecutiveNoProgress: external_exports.number().int().nonnegative(),
10123
+ lastScroll: external_exports.enum(["moved", "no-progress", "boundary", "unverified"]).optional()
10124
+ }).optional(),
9725
10125
  refreshing: external_exports.boolean().optional(),
9726
10126
  empty: external_exports.boolean().optional(),
9727
10127
  label: boundedString(300).optional(),
@@ -9769,6 +10169,7 @@ var snapshotChoiceGroupSchema = external_exports.object({
9769
10169
  }).passthrough();
9770
10170
  var snapshotElementSchema = external_exports.object({
9771
10171
  id: boundedString(160).optional(),
10172
+ dispatchable: external_exports.boolean().optional(),
9772
10173
  role: boundedString(40).optional(),
9773
10174
  label: boundedString(300).optional(),
9774
10175
  texts: external_exports.array(boundedString(500)).max(50).optional(),
@@ -9831,6 +10232,8 @@ var agentSnapshotSchema = external_exports.object({
9831
10232
  loadingFinished: external_exports.boolean().optional(),
9832
10233
  textsAdded: external_exports.number().int().nonnegative().optional(),
9833
10234
  textsRemoved: external_exports.number().int().nonnegative().optional(),
10235
+ buttonsAddedIndices: external_exports.array(external_exports.number().int().nonnegative()).max(12).optional(),
10236
+ buttonsRemoved: external_exports.number().int().nonnegative().optional(),
9834
10237
  visibleRowsDelta: external_exports.number().int().optional(),
9835
10238
  totalRowsDelta: external_exports.number().int().optional(),
9836
10239
  fieldsNewlyFilled: external_exports.array(boundedString(160)).max(12).optional(),
@@ -9856,6 +10259,16 @@ var agentSnapshotSchema = external_exports.object({
9856
10259
  }).passthrough().optional()
9857
10260
  }).passthrough();
9858
10261
  var agentContextSchema = external_exports.object({
10262
+ missionProtocol: external_exports.literal(1).optional(),
10263
+ missionId: boundedString(160).optional(),
10264
+ /** Preferred supported device language, reported by the SDK independently of map/UI labels. */
10265
+ deviceLocale: external_exports.enum(SUPPORTED_APPILOTS_LOCALES).optional(),
10266
+ /**
10267
+ * Per-session plain-language override (short sentences, no jargon,
10268
+ * numbered steps). Absent → the project's `plainLanguage` setting.
10269
+ * Changes how the reply is WORDED only — see `PLAIN_LANGUAGE_PROMPT`.
10270
+ */
10271
+ plainLanguage: external_exports.boolean().optional(),
9859
10272
  /**
9860
10273
  * Client platform this observation was captured on. Optional and
9861
10274
  * additive (see `clientPlatformSchema`) — absent means
@@ -10106,6 +10519,12 @@ var actionDiagnoseSchema = external_exports.object({
10106
10519
  requiresUserInput: external_exports.boolean().optional()
10107
10520
  });
10108
10521
  var actionResultSchema = external_exports.object({
10522
+ nativeConfirmation: external_exports.object({
10523
+ title: external_exports.string().max(300),
10524
+ message: external_exports.string().max(1e3).optional(),
10525
+ buttonLabel: external_exports.string().max(160).optional(),
10526
+ handlerCompleted: external_exports.boolean()
10527
+ }).optional(),
10109
10528
  actionId: external_exports.string(),
10110
10529
  type: external_exports.string(),
10111
10530
  success: external_exports.boolean(),
@@ -10172,7 +10591,14 @@ var screenPermissionSchema = external_exports.object({
10172
10591
  agentAccess: agentAccessLevelSchema.optional(),
10173
10592
  requiresConfirmation: external_exports.boolean().optional(),
10174
10593
  blockedActions: external_exports.array(external_exports.string().min(1)).optional(),
10175
- isPii: external_exports.boolean().optional()
10594
+ isPii: external_exports.boolean().optional(),
10595
+ /**
10596
+ * Graded assessment (exam, quiz, graded activity). The agent may
10597
+ * explain the screen and navigate away, never write: every action
10598
+ * except navigate/scroll is blocked server-side, and the model is told
10599
+ * to refuse up front instead of trying. Wins over `agentAccess: 'write'`.
10600
+ */
10601
+ assessment: external_exports.boolean().optional()
10176
10602
  }).strict();
10177
10603
  external_exports.object({
10178
10604
  agentCanWrite: external_exports.boolean(),
@@ -10180,6 +10606,10 @@ external_exports.object({
10180
10606
  confirmDestructive: external_exports.boolean(),
10181
10607
  /**
10182
10608
  * Map of screenName → override. Keys must be non-empty strings.
10609
+ * A key containing `*` or starting with `/` is a PATTERN, matched
10610
+ * case-insensitively against the screen name and the navigation path
10611
+ * (`*Quiz*`; a path key uses `*` per segment, see docs/permissions).
10612
+ * An exact key beats patterns.
10183
10613
  * Limited to 200 entries to keep the JSON column small and editable
10184
10614
  * in the dashboard table view.
10185
10615
  */
@@ -10233,7 +10663,7 @@ external_exports.object({
10233
10663
  /** null = remove webhook, undefined = leave unchanged. */
10234
10664
  budgetWebhookUrl: external_exports.string().url().nullable().optional()
10235
10665
  }).strict();
10236
- var localeSchema = external_exports.enum(["pt-BR", "en", "es"]);
10666
+ var localeSchema = external_exports.enum(SUPPORTED_APPILOTS_LOCALES);
10237
10667
  var hexColorSchema = external_exports.string().regex(/^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/, {
10238
10668
  message: "Must be a hex color like #6366f1"
10239
10669
  });
@@ -10277,6 +10707,13 @@ var imageSourceField = external_exports.string().max(500).refine(isSafeImageUrl,
10277
10707
  external_exports.object({
10278
10708
  /** Tone / persona instructions appended to the system prompt. */
10279
10709
  personaPrompt: external_exports.string().max(2048).nullable(),
10710
+ /**
10711
+ * Accessibility: reply in plain language (short sentences, everyday
10712
+ * words, numbered steps, one question at a time). Server-side only,
10713
+ * like `personaPrompt`; a session can override it via
10714
+ * `context.plainLanguage`.
10715
+ */
10716
+ plainLanguage: external_exports.boolean(),
10280
10717
  /** Display name in the chat header (e.g. "Aria"). */
10281
10718
  assistantName: external_exports.string().max(60).nullable(),
10282
10719
  /** URL or remote asset id for the avatar shown next to assistant turns. */
@@ -10300,6 +10737,7 @@ external_exports.object({
10300
10737
  });
10301
10738
  external_exports.object({
10302
10739
  personaPrompt: external_exports.string().max(2048).nullable().optional(),
10740
+ plainLanguage: external_exports.boolean().optional(),
10303
10741
  assistantName: external_exports.string().max(60).nullable().optional(),
10304
10742
  assistantAvatar: imageSourceField.nullable().optional(),
10305
10743
  emptyStateIcon: imageSourceField.nullable().optional(),
@@ -10457,6 +10895,8 @@ external_exports.object({
10457
10895
  * the dev doesn't want their tokens metered.
10458
10896
  */
10459
10897
  silent: external_exports.boolean().optional(),
10898
+ /** Run with the plain-language reply style on/off, overriding the project. */
10899
+ plainLanguage: external_exports.boolean().optional(),
10460
10900
  /**
10461
10901
  * Prior turns of the conversation, in order, that precede `prompt`.
10462
10902
  * Lets a driver run the agent loop hop-by-hop headlessly (the multi-
@@ -11244,9 +11684,22 @@ var MCPGenerator = class _MCPGenerator {
11244
11684
  const filePath = path2__namespace.default.resolve(outputDir, `mcp-document.${this.options.format}`);
11245
11685
  await fs.writeFile(filePath, serialized, "utf-8");
11246
11686
  console.log(`[MCPGenerator] Document written to: ${filePath}`);
11687
+ const controlFiles = analyzed.controlEvidenceFiles ?? {};
11688
+ await fs.writeFile(
11689
+ path2__namespace.default.resolve(outputDir, "control-evidence.json"),
11690
+ JSON.stringify({ version: 1, files: controlFiles }, null, 2),
11691
+ "utf-8"
11692
+ );
11247
11693
  const checksumFilePath = path2__namespace.default.resolve(outputDir, ".appilots-checksum");
11248
11694
  await fs.writeFile(checksumFilePath, checksum, "utf-8");
11249
11695
  console.log(`[MCPGenerator] Checksum written to: ${checksumFilePath}`);
11696
+ const evidenceCount = Object.values(controlFiles).reduce(
11697
+ (sum, entries) => sum + entries.length,
11698
+ 0
11699
+ );
11700
+ console.log(
11701
+ `[MCPGenerator] Source evidence: ${evidenceCount} icon controls across ${Object.keys(controlFiles).length} files (runtime binding required)`
11702
+ );
11250
11703
  console.log("[MCPGenerator] Generation complete!");
11251
11704
  return {
11252
11705
  document,
@@ -11312,7 +11765,8 @@ var KNOWN_CONFIG_KEYS = [
11312
11765
  "navigationExclude",
11313
11766
  "platform",
11314
11767
  "manifestPath",
11315
- "eval"
11768
+ "eval",
11769
+ "knowledge"
11316
11770
  ];
11317
11771
  var KEY_ALIASES = {
11318
11772
  apiUrl: "serverUrl",
@@ -11460,7 +11914,8 @@ function saveConfig(dir, config) {
11460
11914
  navigationExclude: config.navigationExclude || existingConfig?.navigationExclude,
11461
11915
  platform: config.platform || existingConfig?.platform,
11462
11916
  manifestPath: config.manifestPath || existingConfig?.manifestPath,
11463
- eval: config.eval || existingConfig?.eval
11917
+ eval: config.eval || existingConfig?.eval,
11918
+ knowledge: config.knowledge || existingConfig?.knowledge
11464
11919
  };
11465
11920
  try {
11466
11921
  fs$1.writeFileSync(configPath, JSON.stringify(mergedConfig, null, 2), "utf-8");
@@ -11553,6 +12008,19 @@ function validateConfig(config) {
11553
12008
  if (config.manifestPath !== void 0 && typeof config.manifestPath !== "string") {
11554
12009
  errors.push("manifestPath must be a string");
11555
12010
  }
12011
+ if (config.knowledge !== void 0) {
12012
+ if (typeof config.knowledge !== "object" || config.knowledge === null || Array.isArray(config.knowledge)) {
12013
+ errors.push("knowledge must be an object");
12014
+ } else {
12015
+ const kc = config.knowledge;
12016
+ for (const field of ["sources", "exclude"]) {
12017
+ const value = kc[field];
12018
+ if (value !== void 0 && (!Array.isArray(value) || !value.every((item) => typeof item === "string"))) {
12019
+ errors.push(`knowledge.${field} must be an array of strings`);
12020
+ }
12021
+ }
12022
+ }
12023
+ }
11556
12024
  if (config.eval !== void 0) {
11557
12025
  if (typeof config.eval !== "object" || config.eval === null || Array.isArray(config.eval)) {
11558
12026
  errors.push("eval must be an object");
@@ -11829,6 +12297,48 @@ var AppilotsAPIClient = class {
11829
12297
  };
11830
12298
  }
11831
12299
  }
12300
+ /**
12301
+ * Syncs knowledge documents with the Appilots backend.
12302
+ *
12303
+ * @param documents Array of documents to sync (filename, base64 content, mimeType, checksum)
12304
+ * @returns KnowledgeSyncResult with counts of uploaded, skipped, and errored docs
12305
+ */
12306
+ async knowledgeSync(documents) {
12307
+ try {
12308
+ const response = await this.request(`${this.baseUrl}/cli/knowledge/sync`, {
12309
+ method: "POST",
12310
+ headers: {
12311
+ "Content-Type": "application/json",
12312
+ Authorization: `Bearer ${this.apiKey}`
12313
+ },
12314
+ body: JSON.stringify({ documents })
12315
+ });
12316
+ if (!response.ok) {
12317
+ const errorData = await response.json().catch(() => ({}));
12318
+ return {
12319
+ success: false,
12320
+ uploaded: 0,
12321
+ skipped: 0,
12322
+ errors: 0,
12323
+ error: describeApiError(errorData, `HTTP ${response.status}: ${response.statusText}`)
12324
+ };
12325
+ }
12326
+ const json = await response.json();
12327
+ const inner = json.data ?? json;
12328
+ return {
12329
+ success: true,
12330
+ ...inner
12331
+ };
12332
+ } catch (error) {
12333
+ return {
12334
+ success: false,
12335
+ uploaded: 0,
12336
+ skipped: 0,
12337
+ errors: 0,
12338
+ error: error instanceof Error ? error.message : "Failed to sync knowledge with Appilots API"
12339
+ };
12340
+ }
12341
+ }
11832
12342
  /**
11833
12343
  * Checks if the Appilots API server is healthy
11834
12344
  *
@@ -11847,7 +12357,7 @@ var AppilotsAPIClient = class {
11847
12357
  };
11848
12358
 
11849
12359
  // src/version.ts
11850
- var CLI_VERSION = "0.11.3";
12360
+ var CLI_VERSION = "0.14.0";
11851
12361
 
11852
12362
  exports.AppilotsAPIClient = AppilotsAPIClient;
11853
12363
  exports.CLI_VERSION = CLI_VERSION;