@exodus/xqa 1.8.0 → 1.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/xqa.cjs +700 -336
  2. package/package.json +3 -3
package/dist/xqa.cjs CHANGED
@@ -1621,9 +1621,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
1621
1621
  if (fn) {
1622
1622
  this._exitCallback = fn;
1623
1623
  } else {
1624
- this._exitCallback = (err18) => {
1625
- if (err18.code !== "commander.executeSubCommandAsync") {
1626
- throw err18;
1624
+ this._exitCallback = (err19) => {
1625
+ if (err19.code !== "commander.executeSubCommandAsync") {
1626
+ throw err19;
1627
1627
  } else {
1628
1628
  }
1629
1629
  };
@@ -1699,12 +1699,12 @@ Expecting one of '${allowedValues.join("', '")}'`);
1699
1699
  _callParseArg(target, value, previous, invalidArgumentMessage) {
1700
1700
  try {
1701
1701
  return target.parseArg(value, previous);
1702
- } catch (err18) {
1703
- if (err18.code === "commander.invalidArgument") {
1704
- const message = `${invalidArgumentMessage} ${err18.message}`;
1705
- this.error(message, { exitCode: err18.exitCode, code: err18.code });
1702
+ } catch (err19) {
1703
+ if (err19.code === "commander.invalidArgument") {
1704
+ const message = `${invalidArgumentMessage} ${err19.message}`;
1705
+ this.error(message, { exitCode: err19.exitCode, code: err19.code });
1706
1706
  }
1707
- throw err18;
1707
+ throw err19;
1708
1708
  }
1709
1709
  }
1710
1710
  /**
@@ -2293,14 +2293,14 @@ Expecting one of '${allowedValues.join("', '")}'`);
2293
2293
  );
2294
2294
  }
2295
2295
  });
2296
- proc.on("error", (err18) => {
2297
- if (err18.code === "ENOENT") {
2296
+ proc.on("error", (err19) => {
2297
+ if (err19.code === "ENOENT") {
2298
2298
  this._checkForMissingExecutable(
2299
2299
  executableFile,
2300
2300
  executableDir,
2301
2301
  subcommand._name
2302
2302
  );
2303
- } else if (err18.code === "EACCES") {
2303
+ } else if (err19.code === "EACCES") {
2304
2304
  throw new Error(`'${executableFile}' not executable`);
2305
2305
  }
2306
2306
  if (!exitCallback) {
@@ -2311,7 +2311,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
2311
2311
  "commander.executeSubCommandAsync",
2312
2312
  "(error)"
2313
2313
  );
2314
- wrappedError.nestedError = err18;
2314
+ wrappedError.nestedError = err19;
2315
2315
  exitCallback(wrappedError);
2316
2316
  }
2317
2317
  });
@@ -3728,8 +3728,8 @@ var require_index_cjs = __commonJS({
3728
3728
  function okAsync8(value) {
3729
3729
  return new ResultAsync12(Promise.resolve(new Ok(value)));
3730
3730
  }
3731
- function errAsync8(err19) {
3732
- return new ResultAsync12(Promise.resolve(new Err(err19)));
3731
+ function errAsync8(err20) {
3732
+ return new ResultAsync12(Promise.resolve(new Err(err20)));
3733
3733
  }
3734
3734
  var fromPromise = ResultAsync12.fromPromise;
3735
3735
  var fromSafePromise2 = ResultAsync12.fromSafePromise;
@@ -3738,7 +3738,7 @@ var require_index_cjs = __commonJS({
3738
3738
  let acc = ok19([]);
3739
3739
  for (const result of resultList) {
3740
3740
  if (result.isErr()) {
3741
- acc = err18(result.error);
3741
+ acc = err19(result.error);
3742
3742
  break;
3743
3743
  } else {
3744
3744
  acc.map((list) => list.push(result.value));
@@ -3753,7 +3753,7 @@ var require_index_cjs = __commonJS({
3753
3753
  if (result.isErr() && acc.isErr()) {
3754
3754
  acc.error.push(result.error);
3755
3755
  } else if (result.isErr() && acc.isOk()) {
3756
- acc = err18([result.error]);
3756
+ acc = err19([result.error]);
3757
3757
  } else if (result.isOk() && acc.isOk()) {
3758
3758
  acc.value.push(result.value);
3759
3759
  }
@@ -3763,17 +3763,17 @@ var require_index_cjs = __commonJS({
3763
3763
  var combineResultAsyncListWithAllErrors = (asyncResultList) => ResultAsync12.fromSafePromise(Promise.all(asyncResultList)).andThen(combineResultListWithAllErrors);
3764
3764
  exports2.Result = void 0;
3765
3765
  (function(Result3) {
3766
- function fromThrowable14(fn, errorFn) {
3766
+ function fromThrowable16(fn, errorFn) {
3767
3767
  return (...args) => {
3768
3768
  try {
3769
3769
  const result = fn(...args);
3770
3770
  return ok19(result);
3771
3771
  } catch (e3) {
3772
- return err18(errorFn ? errorFn(e3) : e3);
3772
+ return err19(errorFn ? errorFn(e3) : e3);
3773
3773
  }
3774
3774
  };
3775
3775
  }
3776
- Result3.fromThrowable = fromThrowable14;
3776
+ Result3.fromThrowable = fromThrowable16;
3777
3777
  function combine(resultList) {
3778
3778
  return combineResultList(resultList);
3779
3779
  }
@@ -3786,8 +3786,8 @@ var require_index_cjs = __commonJS({
3786
3786
  function ok19(value) {
3787
3787
  return new Ok(value);
3788
3788
  }
3789
- function err18(err19) {
3790
- return new Err(err19);
3789
+ function err19(err20) {
3790
+ return new Err(err20);
3791
3791
  }
3792
3792
  function safeTry(body) {
3793
3793
  const n3 = body().next();
@@ -3882,27 +3882,27 @@ var require_index_cjs = __commonJS({
3882
3882
  }
3883
3883
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
3884
3884
  map(_f) {
3885
- return err18(this.error);
3885
+ return err19(this.error);
3886
3886
  }
3887
3887
  mapErr(f6) {
3888
- return err18(f6(this.error));
3888
+ return err19(f6(this.error));
3889
3889
  }
3890
3890
  andThrough(_f) {
3891
- return err18(this.error);
3891
+ return err19(this.error);
3892
3892
  }
3893
3893
  andTee(_f) {
3894
- return err18(this.error);
3894
+ return err19(this.error);
3895
3895
  }
3896
3896
  orTee(f6) {
3897
3897
  try {
3898
3898
  f6(this.error);
3899
3899
  } catch (e3) {
3900
3900
  }
3901
- return err18(this.error);
3901
+ return err19(this.error);
3902
3902
  }
3903
3903
  // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types
3904
3904
  andThen(_f) {
3905
- return err18(this.error);
3905
+ return err19(this.error);
3906
3906
  }
3907
3907
  // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types
3908
3908
  orElse(f6) {
@@ -3922,13 +3922,13 @@ var require_index_cjs = __commonJS({
3922
3922
  unwrapOr(v2) {
3923
3923
  return v2;
3924
3924
  }
3925
- match(_ok, err19) {
3926
- return err19(this.error);
3925
+ match(_ok, err20) {
3926
+ return err20(this.error);
3927
3927
  }
3928
3928
  safeUnwrap() {
3929
3929
  const error48 = this.error;
3930
3930
  return (function* () {
3931
- yield err18(error48);
3931
+ yield err19(error48);
3932
3932
  throw new Error("Do not use this generator out of `safeTry`");
3933
3933
  })();
3934
3934
  }
@@ -3944,16 +3944,16 @@ var require_index_cjs = __commonJS({
3944
3944
  return self2;
3945
3945
  }
3946
3946
  };
3947
- var fromThrowable13 = exports2.Result.fromThrowable;
3947
+ var fromThrowable15 = exports2.Result.fromThrowable;
3948
3948
  exports2.Err = Err;
3949
3949
  exports2.Ok = Ok;
3950
3950
  exports2.ResultAsync = ResultAsync12;
3951
- exports2.err = err18;
3951
+ exports2.err = err19;
3952
3952
  exports2.errAsync = errAsync8;
3953
3953
  exports2.fromAsyncThrowable = fromAsyncThrowable10;
3954
3954
  exports2.fromPromise = fromPromise;
3955
3955
  exports2.fromSafePromise = fromSafePromise2;
3956
- exports2.fromThrowable = fromThrowable13;
3956
+ exports2.fromThrowable = fromThrowable15;
3957
3957
  exports2.ok = ok19;
3958
3958
  exports2.okAsync = okAsync8;
3959
3959
  exports2.safeTry = safeTry;
@@ -11117,12 +11117,12 @@ var require_lib2 = __commonJS({
11117
11117
  var detectFile = (filepath, opts = {}) => new Promise((resolve, reject) => {
11118
11118
  let fd;
11119
11119
  const fs3 = (0, node_1.default)();
11120
- const handler = (err18, buffer) => {
11120
+ const handler = (err19, buffer) => {
11121
11121
  if (fd) {
11122
11122
  fs3.closeSync(fd);
11123
11123
  }
11124
- if (err18) {
11125
- reject(err18);
11124
+ if (err19) {
11125
+ reject(err19);
11126
11126
  } else if (buffer) {
11127
11127
  resolve((0, exports2.detect)(buffer));
11128
11128
  } else {
@@ -11133,9 +11133,9 @@ var require_lib2 = __commonJS({
11133
11133
  if (sampleSize > 0) {
11134
11134
  fd = fs3.openSync(filepath, "r");
11135
11135
  let sample = Buffer.allocUnsafe(sampleSize);
11136
- fs3.read(fd, sample, 0, sampleSize, opts.offset, (err18, bytesRead) => {
11137
- if (err18) {
11138
- handler(err18, null);
11136
+ fs3.read(fd, sample, 0, sampleSize, opts.offset, (err19, bytesRead) => {
11137
+ if (err19) {
11138
+ handler(err19, null);
11139
11139
  } else {
11140
11140
  if (bytesRead < sampleSize) {
11141
11141
  sample = sample.subarray(0, bytesRead);
@@ -15057,9 +15057,9 @@ var require_main = __commonJS({
15057
15057
  options.path = vaultPath;
15058
15058
  const result = DotenvModule.configDotenv(options);
15059
15059
  if (!result.parsed) {
15060
- const err18 = new Error(`MISSING_DATA: Cannot parse ${vaultPath} for an unknown reason`);
15061
- err18.code = "MISSING_DATA";
15062
- throw err18;
15060
+ const err19 = new Error(`MISSING_DATA: Cannot parse ${vaultPath} for an unknown reason`);
15061
+ err19.code = "MISSING_DATA";
15062
+ throw err19;
15063
15063
  }
15064
15064
  const keys = _dotenvKey(options).split(",");
15065
15065
  const length = keys.length;
@@ -15102,30 +15102,30 @@ var require_main = __commonJS({
15102
15102
  uri = new URL(dotenvKey);
15103
15103
  } catch (error48) {
15104
15104
  if (error48.code === "ERR_INVALID_URL") {
15105
- const err18 = new Error("INVALID_DOTENV_KEY: Wrong format. Must be in valid uri format like dotenv://:key_1234@dotenvx.com/vault/.env.vault?environment=development");
15106
- err18.code = "INVALID_DOTENV_KEY";
15107
- throw err18;
15105
+ const err19 = new Error("INVALID_DOTENV_KEY: Wrong format. Must be in valid uri format like dotenv://:key_1234@dotenvx.com/vault/.env.vault?environment=development");
15106
+ err19.code = "INVALID_DOTENV_KEY";
15107
+ throw err19;
15108
15108
  }
15109
15109
  throw error48;
15110
15110
  }
15111
15111
  const key = uri.password;
15112
15112
  if (!key) {
15113
- const err18 = new Error("INVALID_DOTENV_KEY: Missing key part");
15114
- err18.code = "INVALID_DOTENV_KEY";
15115
- throw err18;
15113
+ const err19 = new Error("INVALID_DOTENV_KEY: Missing key part");
15114
+ err19.code = "INVALID_DOTENV_KEY";
15115
+ throw err19;
15116
15116
  }
15117
15117
  const environment = uri.searchParams.get("environment");
15118
15118
  if (!environment) {
15119
- const err18 = new Error("INVALID_DOTENV_KEY: Missing environment part");
15120
- err18.code = "INVALID_DOTENV_KEY";
15121
- throw err18;
15119
+ const err19 = new Error("INVALID_DOTENV_KEY: Missing environment part");
15120
+ err19.code = "INVALID_DOTENV_KEY";
15121
+ throw err19;
15122
15122
  }
15123
15123
  const environmentKey = `DOTENV_VAULT_${environment.toUpperCase()}`;
15124
15124
  const ciphertext = result.parsed[environmentKey];
15125
15125
  if (!ciphertext) {
15126
- const err18 = new Error(`NOT_FOUND_DOTENV_ENVIRONMENT: Cannot locate environment ${environmentKey} in your .env.vault file.`);
15127
- err18.code = "NOT_FOUND_DOTENV_ENVIRONMENT";
15128
- throw err18;
15126
+ const err19 = new Error(`NOT_FOUND_DOTENV_ENVIRONMENT: Cannot locate environment ${environmentKey} in your .env.vault file.`);
15127
+ err19.code = "NOT_FOUND_DOTENV_ENVIRONMENT";
15128
+ throw err19;
15129
15129
  }
15130
15130
  return { ciphertext, key };
15131
15131
  }
@@ -15255,13 +15255,13 @@ var require_main = __commonJS({
15255
15255
  const invalidKeyLength = error48.message === "Invalid key length";
15256
15256
  const decryptionFailed = error48.message === "Unsupported state or unable to authenticate data";
15257
15257
  if (isRange || invalidKeyLength) {
15258
- const err18 = new Error("INVALID_DOTENV_KEY: It must be 64 characters long (or more)");
15259
- err18.code = "INVALID_DOTENV_KEY";
15260
- throw err18;
15258
+ const err19 = new Error("INVALID_DOTENV_KEY: It must be 64 characters long (or more)");
15259
+ err19.code = "INVALID_DOTENV_KEY";
15260
+ throw err19;
15261
15261
  } else if (decryptionFailed) {
15262
- const err18 = new Error("DECRYPTION_FAILED: Please check your DOTENV_KEY");
15263
- err18.code = "DECRYPTION_FAILED";
15264
- throw err18;
15262
+ const err19 = new Error("DECRYPTION_FAILED: Please check your DOTENV_KEY");
15263
+ err19.code = "DECRYPTION_FAILED";
15264
+ throw err19;
15265
15265
  } else {
15266
15266
  throw error48;
15267
15267
  }
@@ -15271,9 +15271,9 @@ var require_main = __commonJS({
15271
15271
  const debug = Boolean(options && options.debug);
15272
15272
  const override = Boolean(options && options.override);
15273
15273
  if (typeof parsed !== "object") {
15274
- const err18 = new Error("OBJECT_REQUIRED: Please check the processEnv argument being passed to populate");
15275
- err18.code = "OBJECT_REQUIRED";
15276
- throw err18;
15274
+ const err19 = new Error("OBJECT_REQUIRED: Please check the processEnv argument being passed to populate");
15275
+ err19.code = "OBJECT_REQUIRED";
15276
+ throw err19;
15277
15277
  }
15278
15278
  for (const key of Object.keys(parsed)) {
15279
15279
  if (Object.prototype.hasOwnProperty.call(processEnv, key)) {
@@ -18414,8 +18414,8 @@ var require_picomatch = __commonJS({
18414
18414
  try {
18415
18415
  const opts = options || {};
18416
18416
  return new RegExp(source, opts.flags || (opts.nocase ? "i" : ""));
18417
- } catch (err18) {
18418
- if (options && options.debug === true) throw err18;
18417
+ } catch (err19) {
18418
+ if (options && options.debug === true) throw err19;
18419
18419
  return /$^/;
18420
18420
  }
18421
18421
  };
@@ -18822,8 +18822,8 @@ var require_merge2 = __commonJS({
18822
18822
  }
18823
18823
  next();
18824
18824
  }
18825
- function onerror(err18) {
18826
- mergedStream.emit("error", err18);
18825
+ function onerror(err19) {
18826
+ mergedStream.emit("error", err19);
18827
18827
  }
18828
18828
  if (stream._readableState.endEmitted) {
18829
18829
  return next();
@@ -19187,8 +19187,8 @@ var require_out = __commonJS({
19187
19187
  var require_queue_microtask = __commonJS({
19188
19188
  "../../node_modules/.pnpm/queue-microtask@1.2.3/node_modules/queue-microtask/index.js"(exports2, module2) {
19189
19189
  var promise2;
19190
- module2.exports = typeof queueMicrotask === "function" ? queueMicrotask.bind(typeof window !== "undefined" ? window : global) : (cb2) => (promise2 || (promise2 = Promise.resolve())).then(cb2).catch((err18) => setTimeout(() => {
19191
- throw err18;
19190
+ module2.exports = typeof queueMicrotask === "function" ? queueMicrotask.bind(typeof window !== "undefined" ? window : global) : (cb2) => (promise2 || (promise2 = Promise.resolve())).then(cb2).catch((err19) => setTimeout(() => {
19191
+ throw err19;
19192
19192
  }, 0));
19193
19193
  }
19194
19194
  });
@@ -19209,32 +19209,32 @@ var require_run_parallel = __commonJS({
19209
19209
  results = {};
19210
19210
  pending = keys.length;
19211
19211
  }
19212
- function done(err18) {
19212
+ function done(err19) {
19213
19213
  function end() {
19214
- if (cb2) cb2(err18, results);
19214
+ if (cb2) cb2(err19, results);
19215
19215
  cb2 = null;
19216
19216
  }
19217
19217
  if (isSync) queueMicrotask2(end);
19218
19218
  else end();
19219
19219
  }
19220
- function each(i3, err18, result) {
19220
+ function each(i3, err19, result) {
19221
19221
  results[i3] = result;
19222
- if (--pending === 0 || err18) {
19223
- done(err18);
19222
+ if (--pending === 0 || err19) {
19223
+ done(err19);
19224
19224
  }
19225
19225
  }
19226
19226
  if (!pending) {
19227
19227
  done(null);
19228
19228
  } else if (keys) {
19229
19229
  keys.forEach(function(key) {
19230
- tasks[key](function(err18, result) {
19231
- each(key, err18, result);
19230
+ tasks[key](function(err19, result) {
19231
+ each(key, err19, result);
19232
19232
  });
19233
19233
  });
19234
19234
  } else {
19235
19235
  tasks.forEach(function(task, i3) {
19236
- task(function(err18, result) {
19237
- each(i3, err18, result);
19236
+ task(function(err19, result) {
19237
+ each(i3, err19, result);
19238
19238
  });
19239
19239
  });
19240
19240
  }
@@ -19806,16 +19806,16 @@ var require_queue = __commonJS({
19806
19806
  this.context = null;
19807
19807
  this.errorHandler = null;
19808
19808
  var self2 = this;
19809
- this.worked = function worked(err18, result) {
19809
+ this.worked = function worked(err19, result) {
19810
19810
  var callback = self2.callback;
19811
19811
  var errorHandler = self2.errorHandler;
19812
19812
  var val = self2.value;
19813
19813
  self2.value = null;
19814
19814
  self2.callback = noop2;
19815
19815
  if (self2.errorHandler) {
19816
- errorHandler(err18, val);
19816
+ errorHandler(err19, val);
19817
19817
  }
19818
- callback.call(self2.context, err18, result);
19818
+ callback.call(self2.context, err19, result);
19819
19819
  self2.release(self2);
19820
19820
  };
19821
19821
  }
@@ -19839,9 +19839,9 @@ var require_queue = __commonJS({
19839
19839
  return queue;
19840
19840
  function push(value) {
19841
19841
  var p = new Promise(function(resolve, reject) {
19842
- pushCb(value, function(err18, result) {
19843
- if (err18) {
19844
- reject(err18);
19842
+ pushCb(value, function(err19, result) {
19843
+ if (err19) {
19844
+ reject(err19);
19845
19845
  return;
19846
19846
  }
19847
19847
  resolve(result);
@@ -19852,9 +19852,9 @@ var require_queue = __commonJS({
19852
19852
  }
19853
19853
  function unshift(value) {
19854
19854
  var p = new Promise(function(resolve, reject) {
19855
- unshiftCb(value, function(err18, result) {
19856
- if (err18) {
19857
- reject(err18);
19855
+ unshiftCb(value, function(err19, result) {
19856
+ if (err19) {
19857
+ reject(err19);
19858
19858
  return;
19859
19859
  }
19860
19860
  resolve(result);
@@ -21075,7 +21075,7 @@ var {
21075
21075
  } = import_index.default;
21076
21076
 
21077
21077
  // src/commands/analyse-command.ts
21078
- var import_promises16 = require("node:fs/promises");
21078
+ var import_promises17 = require("node:fs/promises");
21079
21079
 
21080
21080
  // ../../packages/pipeline/dist/index.js
21081
21081
  var import_node_fs2 = require("node:fs");
@@ -51989,6 +51989,7 @@ var import_node_path = __toESM(require("node:path"), 1);
51989
51989
  var import_neverthrow5 = __toESM(require_index_cjs(), 1);
51990
51990
  var import_sharp = __toESM(require("sharp"), 1);
51991
51991
  var import_neverthrow6 = __toESM(require_index_cjs(), 1);
51992
+ var import_promises6 = require("node:timers/promises");
51992
51993
  var import_node_crypto = require("node:crypto");
51993
51994
  var import_node_child_process2 = require("node:child_process");
51994
51995
  var import_node_events = require("node:events");
@@ -52052,11 +52053,33 @@ function resolveUdid(udid) {
52052
52053
  return (0, import_neverthrow3.err)(result);
52053
52054
  });
52054
52055
  }
52056
+ var parseIdbTree = (0, import_neverthrow.fromThrowable)(
52057
+ (raw) => JSON.parse(raw),
52058
+ (cause) => ({ type: "SNAPSHOT_PARSE_FAILED", cause })
52059
+ );
52055
52060
  var FULLSCREEN_THRESHOLD = 0.9;
52056
52061
  var DEFAULT_SCREEN_WIDTH = 375;
52057
52062
  var DEFAULT_SCREEN_HEIGHT = 812;
52058
- var STABILITY_POLL_ATTEMPTS = 5;
52059
- var STABILITY_POLL_DELAY_MS = 300;
52063
+ var STABILITY_POLL_ATTEMPTS = 10;
52064
+ var STABILITY_POLL_DELAY_MS = 500;
52065
+ function structuralSignature(elements) {
52066
+ const lines = [];
52067
+ const walk = (nodes) => {
52068
+ for (const element of nodes) {
52069
+ const frame = element.frame ?? { x: 0, y: 0, width: 0, height: 0 };
52070
+ const type2 = element.type ?? "unknown";
52071
+ const childCount = (element.children ?? []).length;
52072
+ lines.push(
52073
+ `${type2}|${String(frame.x)},${String(frame.y)},${String(frame.width)},${String(frame.height)}|${String(childCount)}`
52074
+ );
52075
+ if (element.children) {
52076
+ walk(element.children);
52077
+ }
52078
+ }
52079
+ };
52080
+ walk(elements);
52081
+ return lines.join("\n");
52082
+ }
52060
52083
  function isInViewport(frame, screen) {
52061
52084
  return frame.width > 0 && frame.height > 0 && frame.x + frame.width > 0 && frame.x < screen.width && frame.y + frame.height > 0 && frame.y < screen.height;
52062
52085
  }
@@ -52168,36 +52191,39 @@ function formatAccessibilityElements(elements) {
52168
52191
 
52169
52192
  ${elementList}` : elementList;
52170
52193
  }
52194
+ function fetchSnapshot(udid) {
52195
+ return runCommand("idb", ["ui", "describe-all", "--udid", udid, "--json", "--nested"]);
52196
+ }
52171
52197
  async function pollUntilStable(udid) {
52172
- let previous;
52198
+ let previousSignature;
52199
+ let previousRaw = "";
52173
52200
  for (let index = 0; index < STABILITY_POLL_ATTEMPTS; index++) {
52174
- const result = await runCommand("idb", [
52175
- "ui",
52176
- "describe-all",
52177
- "--udid",
52178
- udid,
52179
- "--json",
52180
- "--nested"
52181
- ]);
52201
+ const result = await fetchSnapshot(udid);
52182
52202
  if (result.isErr()) {
52183
52203
  return result;
52184
52204
  }
52185
- const current = result.value;
52186
- if (current === previous) {
52187
- return (0, import_neverthrow.ok)(current);
52205
+ const raw = result.value;
52206
+ const parsed = parseIdbTree(raw);
52207
+ if (parsed.isErr()) {
52208
+ return (0, import_neverthrow.err)(parsed.error);
52209
+ }
52210
+ const signature = structuralSignature(parsed.value);
52211
+ if (signature === previousSignature) {
52212
+ return (0, import_neverthrow.ok)(raw);
52188
52213
  }
52189
- previous = current;
52214
+ previousSignature = signature;
52215
+ previousRaw = raw;
52190
52216
  if (index < STABILITY_POLL_ATTEMPTS - 1) {
52191
52217
  await (0, import_promises4.setTimeout)(STABILITY_POLL_DELAY_MS);
52192
52218
  }
52193
52219
  }
52194
- return (0, import_neverthrow.ok)(previous ?? "");
52220
+ return (0, import_neverthrow.ok)(previousRaw);
52195
52221
  }
52196
52222
  function waitForStableSnapshot(udid) {
52197
52223
  return new import_neverthrow.ResultAsync(pollUntilStable(udid));
52198
52224
  }
52199
52225
  function captureAccessibilityTree(udid = "booted") {
52200
- return resolveUdid(udid).andThen((resolvedUdid) => waitForStableSnapshot(resolvedUdid)).map((raw) => JSON.parse(raw));
52226
+ return resolveUdid(udid).andThen((resolvedUdid) => waitForStableSnapshot(resolvedUdid)).andThen((raw) => parseIdbTree(raw));
52201
52227
  }
52202
52228
  function captureAccessibilitySnapshot(udid = "booted") {
52203
52229
  return captureAccessibilityTree(udid).map((elements) => formatAccessibilityElements(elements));
@@ -52590,6 +52616,37 @@ function createScreenshotTool(udid = "booted") {
52590
52616
  });
52591
52617
  }
52592
52618
  var screenshotTool = createScreenshotTool();
52619
+ var MIN_WAIT_SECONDS = 1;
52620
+ var MAX_WAIT_SECONDS = 10;
52621
+ var MS_PER_SECOND2 = 1e3;
52622
+ var SECONDS_SCHEMA = external_exports.number().int().min(MIN_WAIT_SECONDS).max(MAX_WAIT_SECONDS);
52623
+ var WAIT_SCHEMA = {
52624
+ seconds: SECONDS_SCHEMA
52625
+ };
52626
+ function createWaitSecondsTool() {
52627
+ return _x(
52628
+ "wait_seconds",
52629
+ "Pause for N seconds (1-10) when expected UI content may still be loading silently. Call view_ui again after waiting.",
52630
+ WAIT_SCHEMA,
52631
+ async ({ seconds }) => {
52632
+ const parsed = SECONDS_SCHEMA.safeParse(seconds);
52633
+ if (!parsed.success) {
52634
+ return {
52635
+ content: [
52636
+ {
52637
+ type: "text",
52638
+ text: `wait_seconds expected an integer between ${String(MIN_WAIT_SECONDS)} and ${String(MAX_WAIT_SECONDS)}, got ${String(seconds)}`
52639
+ }
52640
+ ],
52641
+ isError: true
52642
+ };
52643
+ }
52644
+ await (0, import_promises6.setTimeout)(parsed.data * MS_PER_SECOND2);
52645
+ return { content: [{ type: "text", text: `Waited ${String(parsed.data)}s` }] };
52646
+ }
52647
+ );
52648
+ }
52649
+ var WAIT_SECONDS_TOOL_NAME = "mcp__mobile-ios__wait_seconds";
52593
52650
  var LABEL_SET_CAP = 50;
52594
52651
  var MID_DEPTH_THRESHOLD = 5;
52595
52652
  var DEFAULT_SCREEN_HEIGHT2 = 812;
@@ -52744,6 +52801,7 @@ function createMobileIosServer(udid = "booted", extraTools = []) {
52744
52801
  createLaunchAppTool(udid),
52745
52802
  createTerminateAppTool(udid),
52746
52803
  createListAppsTool(udid),
52804
+ createWaitSecondsTool(),
52747
52805
  ...extraTools
52748
52806
  ]
52749
52807
  });
@@ -52765,8 +52823,7 @@ var findingSchema = external_exports.object({
52765
52823
  "motion-regression",
52766
52824
  "interaction-regression",
52767
52825
  "continuity-regression",
52768
- "hint-drift",
52769
- "step-skipped"
52826
+ "missing-content"
52770
52827
  ]),
52771
52828
  flow: external_exports.string(),
52772
52829
  steps: external_exports.array(external_exports.string()),
@@ -52811,10 +52868,10 @@ function dismissalsPath(baseDirectory, override) {
52811
52868
 
52812
52869
  // ../../packages/pipeline/dist/index.js
52813
52870
  var import_neverthrow30 = __toESM(require_index_cjs(), 1);
52814
- var import_promises14 = require("node:timers/promises");
52871
+ var import_promises15 = require("node:timers/promises");
52815
52872
 
52816
52873
  // ../../agents/analyser/dist/index.js
52817
- var import_promises6 = require("node:fs/promises");
52874
+ var import_promises7 = require("node:fs/promises");
52818
52875
 
52819
52876
  // ../../node_modules/.pnpm/chalk@5.6.2/node_modules/chalk/source/vendor/ansi-styles/index.js
52820
52877
  var ANSI_BACKGROUND_OFFSET = 10;
@@ -53640,9 +53697,9 @@ function render(state, previous) {
53640
53697
  function dashboardEqual(left, right) {
53641
53698
  return JSON.stringify(left) === JSON.stringify(right);
53642
53699
  }
53643
- var MS_PER_SECOND2 = 1e3;
53700
+ var MS_PER_SECOND3 = 1e3;
53644
53701
  function formatDuration(ms) {
53645
- return `${String(Math.round(ms / MS_PER_SECOND2))}s`;
53702
+ return `${String(Math.round(ms / MS_PER_SECOND3))}s`;
53646
53703
  }
53647
53704
  function normalizeItemQueued(event) {
53648
53705
  return [{ kind: "text", style: "default", text: `[${event.item.name}] QUEUED` }];
@@ -54478,7 +54535,7 @@ function safeHandler(handler) {
54478
54535
 
54479
54536
  // ../../agents/analyser/dist/index.js
54480
54537
  var import_neverthrow10 = __toESM(require_index_cjs(), 1);
54481
- var import_promises7 = require("node:timers/promises");
54538
+ var import_promises8 = require("node:timers/promises");
54482
54539
 
54483
54540
  // ../../node_modules/.pnpm/@google+generative-ai@0.24.1/node_modules/@google/generative-ai/dist/index.mjs
54484
54541
  var SchemaType;
@@ -54681,15 +54738,15 @@ async function makeRequest(url2, fetchOptions, fetchFn = fetch) {
54681
54738
  return response;
54682
54739
  }
54683
54740
  function handleResponseError(e3, url2) {
54684
- let err18 = e3;
54685
- if (err18.name === "AbortError") {
54686
- err18 = new GoogleGenerativeAIAbortError(`Request aborted when fetching ${url2.toString()}: ${e3.message}`);
54687
- err18.stack = e3.stack;
54741
+ let err19 = e3;
54742
+ if (err19.name === "AbortError") {
54743
+ err19 = new GoogleGenerativeAIAbortError(`Request aborted when fetching ${url2.toString()}: ${e3.message}`);
54744
+ err19.stack = e3.stack;
54688
54745
  } else if (!(e3 instanceof GoogleGenerativeAIFetchError || e3 instanceof GoogleGenerativeAIRequestInputError)) {
54689
- err18 = new GoogleGenerativeAIError(`Error fetching from ${url2.toString()}: ${e3.message}`);
54690
- err18.stack = e3.stack;
54746
+ err19 = new GoogleGenerativeAIError(`Error fetching from ${url2.toString()}: ${e3.message}`);
54747
+ err19.stack = e3.stack;
54691
54748
  }
54692
- throw err18;
54749
+ throw err19;
54693
54750
  }
54694
54751
  async function handleResponseNotOk(response, url2) {
54695
54752
  let message = "";
@@ -54936,14 +54993,14 @@ function getResponseStream(inputStream) {
54936
54993
  }
54937
54994
  return pump();
54938
54995
  }).catch((e3) => {
54939
- let err18 = e3;
54940
- err18.stack = e3.stack;
54941
- if (err18.name === "AbortError") {
54942
- err18 = new GoogleGenerativeAIAbortError("Request aborted when reading from the stream");
54996
+ let err19 = e3;
54997
+ err19.stack = e3.stack;
54998
+ if (err19.name === "AbortError") {
54999
+ err19 = new GoogleGenerativeAIAbortError("Request aborted when reading from the stream");
54943
55000
  } else {
54944
- err18 = new GoogleGenerativeAIError("Error reading from the stream");
55001
+ err19 = new GoogleGenerativeAIError("Error reading from the stream");
54945
55002
  }
54946
- throw err18;
55003
+ throw err19;
54947
55004
  });
54948
55005
  }
54949
55006
  }
@@ -55539,15 +55596,15 @@ async function makeRequest2(url2, fetchOptions, fetchFn = fetch) {
55539
55596
  return response;
55540
55597
  }
55541
55598
  function handleResponseError2(e3, url2) {
55542
- let err18 = e3;
55543
- if (err18.name === "AbortError") {
55544
- err18 = new GoogleGenerativeAIAbortError2(`Request aborted when fetching ${url2.toString()}: ${e3.message}`);
55545
- err18.stack = e3.stack;
55599
+ let err19 = e3;
55600
+ if (err19.name === "AbortError") {
55601
+ err19 = new GoogleGenerativeAIAbortError2(`Request aborted when fetching ${url2.toString()}: ${e3.message}`);
55602
+ err19.stack = e3.stack;
55546
55603
  } else if (!(e3 instanceof GoogleGenerativeAIFetchError2 || e3 instanceof GoogleGenerativeAIRequestInputError2)) {
55547
- err18 = new GoogleGenerativeAIError2(`Error fetching from ${url2.toString()}: ${e3.message}`);
55548
- err18.stack = e3.stack;
55604
+ err19 = new GoogleGenerativeAIError2(`Error fetching from ${url2.toString()}: ${e3.message}`);
55605
+ err19.stack = e3.stack;
55549
55606
  }
55550
- throw err18;
55607
+ throw err19;
55551
55608
  }
55552
55609
  async function handleResponseNotOk2(response, url2) {
55553
55610
  let message = "";
@@ -55907,7 +55964,7 @@ async function runPollLoop(checkState, onTick) {
55907
55964
  if (state === FileState.FAILED) {
55908
55965
  return "failed";
55909
55966
  }
55910
- await (0, import_promises7.setTimeout)(POLL_INTERVAL_MS);
55967
+ await (0, import_promises8.setTimeout)(POLL_INTERVAL_MS);
55911
55968
  }
55912
55969
  return "timeout";
55913
55970
  }
@@ -56037,7 +56094,7 @@ var DEFAULT_MODEL = "gemini-2.5-pro";
56037
56094
  var BYTES_PER_MB = 1048576;
56038
56095
  var MAX_POLL_ATTEMPTS2 = 30;
56039
56096
  async function getFileSize(filePath) {
56040
- const fileStats = await (0, import_promises6.stat)(filePath);
56097
+ const fileStats = await (0, import_promises7.stat)(filePath);
56041
56098
  return fileStats.size;
56042
56099
  }
56043
56100
  function emitStageEnd(onEvent, start) {
@@ -56143,34 +56200,34 @@ var uuid42 = function() {
56143
56200
  };
56144
56201
 
56145
56202
  // ../../node_modules/.pnpm/@anthropic-ai+sdk@0.87.0_zod@4.3.6/node_modules/@anthropic-ai/sdk/internal/errors.mjs
56146
- function isAbortError(err18) {
56147
- return typeof err18 === "object" && err18 !== null && // Spec-compliant fetch implementations
56148
- ("name" in err18 && err18.name === "AbortError" || // Expo fetch
56149
- "message" in err18 && String(err18.message).includes("FetchRequestCanceledException"));
56150
- }
56151
- var castToError = (err18) => {
56152
- if (err18 instanceof Error)
56153
- return err18;
56154
- if (typeof err18 === "object" && err18 !== null) {
56203
+ function isAbortError(err19) {
56204
+ return typeof err19 === "object" && err19 !== null && // Spec-compliant fetch implementations
56205
+ ("name" in err19 && err19.name === "AbortError" || // Expo fetch
56206
+ "message" in err19 && String(err19.message).includes("FetchRequestCanceledException"));
56207
+ }
56208
+ var castToError = (err19) => {
56209
+ if (err19 instanceof Error)
56210
+ return err19;
56211
+ if (typeof err19 === "object" && err19 !== null) {
56155
56212
  try {
56156
- if (Object.prototype.toString.call(err18) === "[object Error]") {
56157
- const error48 = new Error(err18.message, err18.cause ? { cause: err18.cause } : {});
56158
- if (err18.stack)
56159
- error48.stack = err18.stack;
56160
- if (err18.cause && !error48.cause)
56161
- error48.cause = err18.cause;
56162
- if (err18.name)
56163
- error48.name = err18.name;
56213
+ if (Object.prototype.toString.call(err19) === "[object Error]") {
56214
+ const error48 = new Error(err19.message, err19.cause ? { cause: err19.cause } : {});
56215
+ if (err19.stack)
56216
+ error48.stack = err19.stack;
56217
+ if (err19.cause && !error48.cause)
56218
+ error48.cause = err19.cause;
56219
+ if (err19.name)
56220
+ error48.name = err19.name;
56164
56221
  return error48;
56165
56222
  }
56166
56223
  } catch {
56167
56224
  }
56168
56225
  try {
56169
- return new Error(JSON.stringify(err18));
56226
+ return new Error(JSON.stringify(err19));
56170
56227
  } catch {
56171
56228
  }
56172
56229
  }
56173
- return new Error(err18);
56230
+ return new Error(err19);
56174
56231
  };
56175
56232
 
56176
56233
  // ../../node_modules/.pnpm/@anthropic-ai+sdk@0.87.0_zod@4.3.6/node_modules/@anthropic-ai/sdk/core/error.mjs
@@ -56300,13 +56357,13 @@ var validatePositiveInteger = (name, n3) => {
56300
56357
  var safeJSON = (text) => {
56301
56358
  try {
56302
56359
  return JSON.parse(text);
56303
- } catch (err18) {
56360
+ } catch (err19) {
56304
56361
  return void 0;
56305
56362
  }
56306
56363
  };
56307
56364
 
56308
56365
  // ../../node_modules/.pnpm/@anthropic-ai+sdk@0.87.0_zod@4.3.6/node_modules/@anthropic-ai/sdk/internal/utils/sleep.mjs
56309
- var sleep3 = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
56366
+ var sleep4 = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
56310
56367
 
56311
56368
  // ../../node_modules/.pnpm/@anthropic-ai+sdk@0.87.0_zod@4.3.6/node_modules/@anthropic-ai/sdk/version.mjs
56312
56369
  var VERSION = "0.87.0";
@@ -56864,8 +56921,8 @@ var Stream = class _Stream {
56864
56921
  return ctrl.close();
56865
56922
  const bytes = encodeUTF8(JSON.stringify(value) + "\n");
56866
56923
  ctrl.enqueue(bytes);
56867
- } catch (err18) {
56868
- ctrl.error(err18);
56924
+ } catch (err19) {
56925
+ ctrl.error(err19);
56869
56926
  }
56870
56927
  },
56871
56928
  async cancel() {
@@ -58772,8 +58829,8 @@ var BetaMessageStream = class _BetaMessageStream {
58772
58829
  if (jsonBuf) {
58773
58830
  try {
58774
58831
  newContent.input = partialParse(jsonBuf);
58775
- } catch (err18) {
58776
- const error48 = new AnthropicError(`Unable to parse tool parameter JSON from model. Please retry your request or adjust your prompt. Error: ${err18}. JSON: ${jsonBuf}`);
58832
+ } catch (err19) {
58833
+ const error48 = new AnthropicError(`Unable to parse tool parameter JSON from model. Please retry your request or adjust your prompt. Error: ${err19}. JSON: ${jsonBuf}`);
58777
58834
  __classPrivateFieldGet(this, _BetaMessageStream_handleError, "f").call(this, error48);
58778
58835
  }
58779
58836
  }
@@ -58835,17 +58892,17 @@ var BetaMessageStream = class _BetaMessageStream {
58835
58892
  }
58836
58893
  readQueue.length = 0;
58837
58894
  });
58838
- this.on("abort", (err18) => {
58895
+ this.on("abort", (err19) => {
58839
58896
  done = true;
58840
58897
  for (const reader of readQueue) {
58841
- reader.reject(err18);
58898
+ reader.reject(err19);
58842
58899
  }
58843
58900
  readQueue.length = 0;
58844
58901
  });
58845
- this.on("error", (err18) => {
58902
+ this.on("error", (err19) => {
58846
58903
  done = true;
58847
58904
  for (const reader of readQueue) {
58848
- reader.reject(err18);
58905
+ reader.reject(err19);
58849
58906
  }
58850
58907
  readQueue.length = 0;
58851
58908
  });
@@ -61088,17 +61145,17 @@ var MessageStream = class _MessageStream {
61088
61145
  }
61089
61146
  readQueue.length = 0;
61090
61147
  });
61091
- this.on("abort", (err18) => {
61148
+ this.on("abort", (err19) => {
61092
61149
  done = true;
61093
61150
  for (const reader of readQueue) {
61094
- reader.reject(err18);
61151
+ reader.reject(err19);
61095
61152
  }
61096
61153
  readQueue.length = 0;
61097
61154
  });
61098
- this.on("error", (err18) => {
61155
+ this.on("error", (err19) => {
61099
61156
  done = true;
61100
61157
  for (const reader of readQueue) {
61101
- reader.reject(err18);
61158
+ reader.reject(err19);
61102
61159
  }
61103
61160
  readQueue.length = 0;
61104
61161
  });
@@ -61678,7 +61735,7 @@ var BaseAnthropic = class {
61678
61735
  }
61679
61736
  const retryMessage = shouldRetry ? `error; no more retries left` : `error; not retryable`;
61680
61737
  loggerFor(this).info(`${responseInfo} - ${retryMessage}`);
61681
- const errText = await response.text().catch((err19) => castToError(err19).message);
61738
+ const errText = await response.text().catch((err20) => castToError(err20).message);
61682
61739
  const errJSON = safeJSON(errText);
61683
61740
  const errMessage = errJSON ? void 0 : errText;
61684
61741
  loggerFor(this).debug(`[${requestLogID}] response error (${retryMessage})`, formatRequestDetails({
@@ -61689,8 +61746,8 @@ var BaseAnthropic = class {
61689
61746
  message: errMessage,
61690
61747
  durationMs: Date.now() - startTime
61691
61748
  }));
61692
- const err18 = this.makeStatusError(response.status, errJSON, errMessage, response.headers);
61693
- throw err18;
61749
+ const err19 = this.makeStatusError(response.status, errJSON, errMessage, response.headers);
61750
+ throw err19;
61694
61751
  }
61695
61752
  loggerFor(this).info(responseInfo);
61696
61753
  loggerFor(this).debug(`[${requestLogID}] response start`, formatRequestDetails({
@@ -61769,7 +61826,7 @@ var BaseAnthropic = class {
61769
61826
  const maxRetries = options.maxRetries ?? this.maxRetries;
61770
61827
  timeoutMillis = this.calculateDefaultRetryTimeoutMillis(retriesRemaining, maxRetries);
61771
61828
  }
61772
- await sleep3(timeoutMillis);
61829
+ await sleep4(timeoutMillis);
61773
61830
  return this.makeRequest(options, retriesRemaining - 1, requestLogID);
61774
61831
  }
61775
61832
  calculateDefaultRetryTimeoutMillis(retriesRemaining, maxRetries) {
@@ -62133,25 +62190,25 @@ var import_neverthrow31 = __toESM(require_index_cjs(), 1);
62133
62190
  var import_neverthrow32 = __toESM(require_index_cjs(), 1);
62134
62191
  var import_node_fs3 = require("node:fs");
62135
62192
  var import_neverthrow33 = __toESM(require_index_cjs(), 1);
62136
- var import_promises15 = require("node:timers/promises");
62193
+ var import_promises16 = require("node:timers/promises");
62137
62194
 
62138
62195
  // ../../agents/explorer/dist/index.js
62139
62196
  var import_node_child_process3 = require("node:child_process");
62140
62197
  var import_node_fs = require("node:fs");
62141
62198
  var import_node_path4 = __toESM(require("node:path"), 1);
62142
62199
  var import_neverthrow15 = __toESM(require_index_cjs(), 1);
62143
- var import_promises8 = require("node:timers/promises");
62200
+ var import_promises9 = require("node:timers/promises");
62144
62201
  var import_neverthrow16 = __toESM(require_index_cjs(), 1);
62145
62202
  var import_neverthrow17 = __toESM(require_index_cjs(), 1);
62146
- var import_promises9 = require("node:fs/promises");
62203
+ var import_promises10 = require("node:fs/promises");
62147
62204
  var import_node_path5 = __toESM(require("node:path"), 1);
62148
62205
  var import_neverthrow18 = __toESM(require_index_cjs(), 1);
62149
62206
  var import_node_child_process4 = require("node:child_process");
62150
- var import_promises10 = require("node:fs/promises");
62207
+ var import_promises11 = require("node:fs/promises");
62151
62208
  var import_neverthrow19 = __toESM(require_index_cjs(), 1);
62152
62209
  var import_neverthrow20 = __toESM(require_index_cjs(), 1);
62153
62210
  var import_neverthrow21 = __toESM(require_index_cjs(), 1);
62154
- var import_promises11 = require("node:fs/promises");
62211
+ var import_promises12 = require("node:fs/promises");
62155
62212
  var import_node_path6 = __toESM(require("node:path"), 1);
62156
62213
  var import_neverthrow22 = __toESM(require_index_cjs(), 1);
62157
62214
  async function runFfmpeg(arguments_) {
@@ -62267,7 +62324,7 @@ function deriveScreenLabel(tree, stepIndex) {
62267
62324
  async function persistScreenshot(params) {
62268
62325
  const screenshotPath = import_node_path5.default.join(params.screenshotsDirectory, `${params.screenLabel}.png`);
62269
62326
  const safeWriteFile = (0, import_neverthrow18.fromAsyncThrowable)(
62270
- import_promises9.writeFile,
62327
+ import_promises10.writeFile,
62271
62328
  (cause) => ({ type: "WRITE_FAILED", cause })
62272
62329
  );
62273
62330
  const writeResult = safeWriteFile(screenshotPath, Buffer.from(params.data, "base64"));
@@ -62332,7 +62389,7 @@ function buildSnapshotRecord(rawElements, { stepIndex, context }) {
62332
62389
  async function handleScreenshotData(data, params) {
62333
62390
  if (params.context.screenshotsDir !== void 0) {
62334
62391
  const safeWrite2 = (0, import_neverthrow18.fromAsyncThrowable)(
62335
- import_promises9.writeFile,
62392
+ import_promises10.writeFile,
62336
62393
  (cause) => ({ type: "WRITE_FAILED", cause })
62337
62394
  );
62338
62395
  const snapshotPath = import_node_path5.default.join(params.context.screenshotsDir, `${params.screenLabel}.txt`);
@@ -62669,7 +62726,8 @@ async function setupQuery(config3, outputTools) {
62669
62726
  const allowedTools = [
62670
62727
  ...config3.allowedTools ?? [],
62671
62728
  ...outputTools.allowedToolNames,
62672
- VIEW_UI_TOOL_NAME
62729
+ VIEW_UI_TOOL_NAME,
62730
+ WAIT_SECONDS_TOOL_NAME
62673
62731
  ];
62674
62732
  const disallowedTools = await resolveDisallowedTools(allowedTools);
62675
62733
  const inputQueue = new MessageQueue();
@@ -62745,7 +62803,7 @@ function startTimeout(timeoutMs, context) {
62745
62803
  var EXPLORER_FINDING_SCHEMA = findingSchema.omit({ agent: true });
62746
62804
  var INTERRUPT_DRAIN_TIMEOUT_MS = 1e4;
62747
62805
  async function interruptOrTimeout(queryRunner) {
62748
- await Promise.race([queryRunner.interrupt(), (0, import_promises8.setTimeout)(INTERRUPT_DRAIN_TIMEOUT_MS)]);
62806
+ await Promise.race([queryRunner.interrupt(), (0, import_promises9.setTimeout)(INTERRUPT_DRAIN_TIMEOUT_MS)]);
62749
62807
  }
62750
62808
  var safeInterruptOrTimeout = import_neverthrow16.ResultAsync.fromThrowable(
62751
62809
  interruptOrTimeout,
@@ -62905,9 +62963,11 @@ At every reasoning step, maintain a mental ledger:
62905
62963
  - PATH: your current navigation stack from root (e.g. Home > Settings > Privacy)
62906
62964
 
62907
62965
  Consult the ledger before every action. Always prefer navigating to a QUEUE screen over a VISITED one.`;
62966
+ var SESSION_START_RULE = `Before taking any other action \u2014 including initializing the Working State ledger or emitting findings \u2014 call \`view_ui\` once to observe the starting screen`;
62908
62967
  var BACK_NAV_RULE = `After navigating forward to any new screen: tap back, call \`view_ui\`, confirm you returned to the expected parent in PATH \u2014 if not, emit a \`back-nav-failure\` finding, then navigate forward again to continue`;
62909
62968
  var STUCK_LOOP_RULE = `Stuck loop: emit a \`stuck-loop\` finding when any of these occur: (1) \`view_ui\` returns the same screen state 3 or more consecutive steps, (2) the same element has been tapped more than twice with no screen change, (3) PATH shows the same screen at two non-adjacent positions \u2014 before emitting, try one alternative action (scroll, long-press, swipe) to rule out a gesture mismatch`;
62910
62969
  var LOADING_STATE_RULE = `Transient loading state: when \`view_ui\` reveals spinners, skeleton screens, progress bars, "Loading..." text, or placeholder content NOT described in spec or app context, wait and retry \`view_ui\` up to 3 times before treating screen as final \u2014 if loading persists after 3 retries, proceed with what is visible; if spec or app context explicitly describes a loading screen as a step, do not retry \u2014 inspect and assert normally`;
62970
+ var EXPECTED_CONTENT_MISSING_RULE = `Expected content missing: when \`view_ui\` shows no loading indicator yet omits an element named or strongly implied by spec or app context \u2014 and its absence is not semantically consistent with the current screen \u2014 call \`wait_seconds\` with 2\u20135 seconds and retry \`view_ui\` up to 2 times; if element remains absent, emit a \`missing-content\` finding stating what was expected and what was observed`;
62911
62971
  var CLIPPED_ELEMENT_RULE = `Never tap an element tagged \`[clipped-top]\`, \`[clipped-bottom]\`, \`[clipped-left]\`, or \`[clipped-right]\` \u2014 scroll to fully reveal it first, then re-call \`view_ui\` before tapping`;
62912
62972
  var WHAT_TO_TEST_SECTION = `## What to Test
62913
62973
 
@@ -62952,9 +63012,7 @@ Each step has this shape:
62952
63012
  - If an outcome state is present, it is your verification target. After acting, call \`view_ui\` and confirm the outcome is met before marking the step complete. If no outcome is given, proceed when the action succeeds.
62953
63013
  - A hint is advisory only. Prefer an element matching the hint, but if no literal match exists, use intent and visual context to select the best candidate. Never fail a step solely because a hint label is absent.
62954
63014
  - Infer element role (primary action, secondary action, dismissal) from visual hierarchy, position, and hint text. Authors do not specify role.
62955
- - If you act without a hint match, emit a \`hint-drift\` finding with the expected hint text and the actual label you acted on. This is non-blocking. Continue execution.
62956
- - If no element satisfies the intent after exhausting visible UI, emit a \`spec-deviation\` finding and halt that step.
62957
- - If \`hint-drift\` is emitted AND the outcome state fails to verify on the same step, escalate to \`spec-deviation\`. Drift plus outcome failure means a structural change, not cosmetic drift.`;
63015
+ - If no element satisfies the intent after exhausting visible UI, emit a \`spec-deviation\` finding and halt that step.`;
62958
63016
  var SPEC_OPTIONAL_STEPS_SECTION = `## Optional Steps
62959
63017
 
62960
63018
  Before executing a step, call \`view_ui\` to observe current screen state.
@@ -62983,11 +63041,13 @@ ${initialState2}` : void 0,
62983
63041
  }
62984
63042
  var SPEC_RULES_SECTION = `## Rules
62985
63043
 
63044
+ - ${SESSION_START_RULE}
62986
63045
  - ALWAYS call \`view_ui\` after every action before deciding what to do next \u2014 it is your only way to observe the screen
62987
63046
  - ${BACK_NAV_RULE}
62988
63047
  - Before selecting any action, prefer navigating to a QUEUE screen over re-exploring a VISITED one
62989
63048
  - ${STUCK_LOOP_RULE}
62990
63049
  - ${LOADING_STATE_RULE}
63050
+ - ${EXPECTED_CONTENT_MISSING_RULE}
62991
63051
  - ${CLIPPED_ELEMENT_RULE}
62992
63052
  - Each item in \`**Assertions**\` is a mandatory pass/fail check \u2014 verify using \`view_ui\`; if the accessibility tree cannot confirm, emit a \`spec-deviation\` finding based on what is observable
62993
63053
  - Flag crash dialogs, unexpected system errors, or navigation failures that occur as a direct result of executing a spec step; if you observe a visibly broken element in passing while navigating, note it without interacting with it`;
@@ -62996,7 +63056,7 @@ function buildSpecModeBody({
62996
63056
  contextBlock,
62997
63057
  environmentSection
62998
63058
  }) {
62999
- return `You are a spec execution agent. Your role is to follow the provided spec exactly \u2014 execute each step in sequence, verify each assertion, and report deviations. Observe and flag obvious breakage encountered in transit, but do not explore or interact with anything outside the spec.
63059
+ return `You are a spec execution agent. Your first action MUST be a \`view_ui\` call. Your role is to follow the provided spec exactly \u2014 execute each step in sequence, verify each assertion, and report deviations. Observe and flag obvious breakage encountered in transit, but do not explore or interact with anything outside the spec.
63000
63060
 
63001
63061
  Verify app against specs below.
63002
63062
 
@@ -63037,17 +63097,19 @@ var FREESTYLE_TEMPLATE = (options) => {
63037
63097
  const environmentSection = buildEnv === "dev" ? `
63038
63098
 
63039
63099
  ${DEV_ENVIRONMENT_SECTION}` : "";
63040
- return `You are a navigation and interaction testing agent. Your role is to find broken navigation flows and non-functional interactive elements. Do not report content bugs, copy errors, or visual style issues unless they directly prevent a navigation action from completing.
63100
+ return `You are a navigation and interaction testing agent. Your first action MUST be a \`view_ui\` call. Your role is to find broken navigation flows and non-functional interactive elements. Do not report content bugs, copy errors, or visual style issues unless they directly prevent a navigation action from completing.
63041
63101
 
63042
63102
  ${contextBlock}
63043
63103
 
63044
63104
  ## Rules
63045
63105
 
63106
+ - ${SESSION_START_RULE}
63046
63107
  - ALWAYS call \`view_ui\` after every action before deciding what to do next \u2014 it is your only way to observe the screen
63047
63108
  - ${BACK_NAV_RULE}
63048
63109
  - Before selecting any action, prefer navigating to a QUEUE screen over re-exploring a VISITED one
63049
63110
  - ${STUCK_LOOP_RULE}
63050
63111
  - ${LOADING_STATE_RULE}
63112
+ - ${EXPECTED_CONTENT_MISSING_RULE}
63051
63113
  - ${CLIPPED_ELEMENT_RULE}
63052
63114
 
63053
63115
  ## Exploration Strategy
@@ -63262,7 +63324,7 @@ function direntToSpecEntry(directory, entry) {
63262
63324
  }
63263
63325
  function scanDirectory(directory) {
63264
63326
  const safeReaddir3 = (0, import_neverthrow22.fromAsyncThrowable)(
63265
- async () => (0, import_promises11.readdir)(directory, { withFileTypes: true }),
63327
+ async () => (0, import_promises12.readdir)(directory, { withFileTypes: true }),
63266
63328
  (cause) => ({ type: "DIR_READ_FAILED", dir: directory, cause })
63267
63329
  );
63268
63330
  return safeReaddir3().orElse((error48) => isNotFound(error48.cause) ? (0, import_neverthrow22.okAsync)([]) : (0, import_neverthrow22.errAsync)(error48)).map(
@@ -63276,7 +63338,7 @@ function readEntries(entries) {
63276
63338
  }
63277
63339
  function readEntry(entry) {
63278
63340
  const safeReadFile6 = (0, import_neverthrow22.fromAsyncThrowable)(
63279
- async () => (0, import_promises11.readFile)(entry.path, "utf8"),
63341
+ async () => (0, import_promises12.readFile)(entry.path, "utf8"),
63280
63342
  (cause) => ({ type: "FILE_READ_FAILED", path: entry.path, cause })
63281
63343
  );
63282
63344
  return safeReadFile6().map((content) => [{ name: entry.name, content }]).orElse((error48) => entry.required ? (0, import_neverthrow22.errAsync)(error48) : (0, import_neverthrow22.okAsync)([]));
@@ -63401,7 +63463,7 @@ function runExplorer(config3) {
63401
63463
  };
63402
63464
  safeConfig.onEvent?.({ type: "STAGE_START", agent: "explorer" });
63403
63465
  return import_neverthrow19.ResultAsync.fromSafePromise(
63404
- (0, import_promises10.mkdir)(runPaths.screenshotsDir, { recursive: true }).catch(() => null)
63466
+ (0, import_promises11.mkdir)(runPaths.screenshotsDir, { recursive: true }).catch(() => null)
63405
63467
  ).andThen(() => runPipeline({ safeConfig, runPaths, start }));
63406
63468
  }
63407
63469
 
@@ -63410,14 +63472,14 @@ var import_neverthrow34 = __toESM(require_index_cjs(), 1);
63410
63472
 
63411
63473
  // ../../agents/inspector/dist/index.js
63412
63474
  var import_neverthrow23 = __toESM(require_index_cjs(), 1);
63413
- var import_promises12 = require("node:fs/promises");
63475
+ var import_promises13 = require("node:fs/promises");
63414
63476
  var import_neverthrow24 = __toESM(require_index_cjs(), 1);
63415
63477
  var import_neverthrow25 = __toESM(require_index_cjs(), 1);
63416
63478
  var import_neverthrow26 = __toESM(require_index_cjs(), 1);
63417
63479
  var import_neverthrow27 = __toESM(require_index_cjs(), 1);
63418
63480
  var import_sharp2 = __toESM(require("sharp"), 1);
63419
63481
  var import_neverthrow28 = __toESM(require_index_cjs(), 1);
63420
- var import_promises13 = require("node:fs/promises");
63482
+ var import_promises14 = require("node:fs/promises");
63421
63483
  var import_node_path7 = __toESM(require("node:path"), 1);
63422
63484
 
63423
63485
  // ../../node_modules/.pnpm/js-yaml@4.1.1/node_modules/js-yaml/dist/js-yaml.mjs
@@ -64383,7 +64445,7 @@ var directiveHandlers = {
64383
64445
  }
64384
64446
  try {
64385
64447
  prefix = decodeURIComponent(prefix);
64386
- } catch (err18) {
64448
+ } catch (err19) {
64387
64449
  throwError(state, "tag prefix is malformed: " + prefix);
64388
64450
  }
64389
64451
  state.tagMap[handle] = prefix;
@@ -65064,7 +65126,7 @@ function readTagProperty(state) {
65064
65126
  }
65065
65127
  try {
65066
65128
  tagName = decodeURIComponent(tagName);
65067
- } catch (err18) {
65129
+ } catch (err19) {
65068
65130
  throwError(state, "tag name is malformed: " + tagName);
65069
65131
  }
65070
65132
  if (isVerbatim) {
@@ -66733,7 +66795,7 @@ async function initArtboardNames({ designStore, config: config3, state }) {
66733
66795
  }
66734
66796
  function readScreenshot(screenshotPath, stepIndex) {
66735
66797
  return import_neverthrow24.ResultAsync.fromThrowable(
66736
- import_promises12.readFile,
66798
+ import_promises13.readFile,
66737
66799
  (cause) => ({ type: "SCREENSHOT_READ_FAILED", stepIndex, cause })
66738
66800
  )(screenshotPath);
66739
66801
  }
@@ -66828,7 +66890,7 @@ function parseMeta(raw) {
66828
66890
  return {};
66829
66891
  }
66830
66892
  async function readAndParseSidecar(sidecarPath) {
66831
- const raw = await (0, import_promises13.readFile)(sidecarPath, "utf8");
66893
+ const raw = await (0, import_promises14.readFile)(sidecarPath, "utf8");
66832
66894
  return parseMeta(raw);
66833
66895
  }
66834
66896
  function readSidecarFile(sidecarPath) {
@@ -66859,7 +66921,7 @@ var FsDesignStore = class {
66859
66921
  }
66860
66922
  listArtboards() {
66861
66923
  return (0, import_neverthrow29.fromAsyncThrowable)(
66862
- import_promises13.readdir,
66924
+ import_promises14.readdir,
66863
66925
  wrapFsError
66864
66926
  )(this.designsDirectory).orElse((fsError) => {
66865
66927
  if (fsError.type === "FS_ERROR" && isEnoent(fsError.cause)) {
@@ -66874,7 +66936,7 @@ var FsDesignStore = class {
66874
66936
  const pngPath = import_node_path7.default.join(this.designsDirectory, `${filename}.png`);
66875
66937
  const sidecarPath = import_node_path7.default.join(this.designsDirectory, `${filename}.meta.yaml`);
66876
66938
  return (0, import_neverthrow29.fromAsyncThrowable)(
66877
- import_promises13.readFile,
66939
+ import_promises14.readFile,
66878
66940
  wrapFsError
66879
66941
  )(pngPath).orElse((fsError) => {
66880
66942
  if (fsError.type === "FS_ERROR" && isEnoent(fsError.cause)) {
@@ -66932,7 +66994,7 @@ function runAnalyserWithRetry(params) {
66932
66994
  ),
66933
66995
  {
66934
66996
  config: { maxAttempts: RETRY_MAX_ATTEMPTS, baseDelayMs: RETRY_BASE_DELAY_MS },
66935
- delayFunction: import_promises14.setTimeout,
66997
+ delayFunction: import_promises15.setTimeout,
66936
66998
  onRetry: ({ attempt, maxAttempts, delayMs, error: error48 }) => {
66937
66999
  onEvent?.({
66938
67000
  type: "AGENT_RETRY",
@@ -67121,7 +67183,7 @@ function runExplorerWithRetry(options) {
67121
67183
  const { explorerConfig, udid, onEvent } = options;
67122
67184
  return withRetry(() => runExplorerWithTeardown(explorerConfig, udid), {
67123
67185
  config: { maxAttempts: RETRY_MAX_ATTEMPTS, baseDelayMs: RETRY_BASE_DELAY_MS },
67124
- delayFunction: import_promises15.setTimeout,
67186
+ delayFunction: import_promises16.setTimeout,
67125
67187
  onRetry: ({ attempt, maxAttempts, delayMs, error: error48 }) => {
67126
67188
  onEvent?.({
67127
67189
  type: "AGENT_RETRY",
@@ -67281,7 +67343,7 @@ function runPipeline2(config3) {
67281
67343
  }
67282
67344
 
67283
67345
  // src/commands/analyse-command.ts
67284
- var import_neverthrow35 = __toESM(require_index_cjs(), 1);
67346
+ var import_neverthrow36 = __toESM(require_index_cjs(), 1);
67285
67347
 
67286
67348
  // src/commands/item-events.ts
67287
67349
  function emitItemStart(identity, at) {
@@ -67316,6 +67378,272 @@ function emitItemFailed(input) {
67316
67378
  });
67317
67379
  }
67318
67380
 
67381
+ // src/shell/debug-agent-events.ts
67382
+ var import_neverthrow35 = __toESM(require_index_cjs(), 1);
67383
+
67384
+ // src/constants.ts
67385
+ var MOBILE_IOS_TOOLS = [
67386
+ "mcp__mobile-ios__tap",
67387
+ "mcp__mobile-ios__double_tap",
67388
+ "mcp__mobile-ios__long_press",
67389
+ "mcp__mobile-ios__swipe",
67390
+ "mcp__mobile-ios__type_text",
67391
+ "mcp__mobile-ios__press_button",
67392
+ "mcp__mobile-ios__launch_app",
67393
+ "mcp__mobile-ios__terminate_app",
67394
+ "mcp__mobile-ios__list_apps",
67395
+ "mcp__mobile-ios__screenshot",
67396
+ "mcp__mobile-ios__accessibility_snapshot"
67397
+ ];
67398
+ var ALLOWED_TOOLS = [...MOBILE_IOS_TOOLS];
67399
+ var MS_PER_SECOND4 = 1e3;
67400
+ var DEFAULT_ABORT_EXIT_CODE = 130;
67401
+ var IDB_INSTALL_MESSAGE = "idb is not installed. Run:\n brew install idb-companion\n pipx install fb-idb\n";
67402
+ var FFMPEG_INSTALL_MESSAGE = "ffmpeg is not installed. Run:\n brew install ffmpeg\n";
67403
+
67404
+ // src/shell/debug-logger-core.ts
67405
+ var ELAPSED_PAD = 8;
67406
+ function formatElapsed(elapsedMs) {
67407
+ const seconds = (elapsedMs / MS_PER_SECOND4).toFixed(2);
67408
+ return `+${seconds}s`.padStart(ELAPSED_PAD, " ");
67409
+ }
67410
+ function createCallTracker() {
67411
+ const starts = /* @__PURE__ */ new Map();
67412
+ return {
67413
+ mark: (key, at) => {
67414
+ const queue = starts.get(key) ?? [];
67415
+ queue.push(at);
67416
+ starts.set(key, queue);
67417
+ },
67418
+ pop: (key) => {
67419
+ const queue = starts.get(key);
67420
+ const start = queue?.shift();
67421
+ if (queue?.length === 0) {
67422
+ starts.delete(key);
67423
+ }
67424
+ return start;
67425
+ }
67426
+ };
67427
+ }
67428
+ function buildLogger(options) {
67429
+ const now = options.now ?? Date.now;
67430
+ const write = options.write ?? ((line) => process.stderr.write(line));
67431
+ const startedAt = now();
67432
+ const tracker = createCallTracker();
67433
+ return {
67434
+ log: (tag, detail) => {
67435
+ const elapsed = now() - startedAt;
67436
+ write(`[debug] ${formatElapsed(elapsed)} ${tag} ${detail}
67437
+ `);
67438
+ },
67439
+ markCall: (key) => {
67440
+ tracker.mark(key, now());
67441
+ },
67442
+ elapsedSince: (key) => {
67443
+ const start = tracker.pop(key);
67444
+ return start === void 0 ? void 0 : now() - start;
67445
+ }
67446
+ };
67447
+ }
67448
+ function formatDurationSuffix(duration3) {
67449
+ if (duration3 === void 0) {
67450
+ return "";
67451
+ }
67452
+ return ` duration=${String(duration3)}ms`;
67453
+ }
67454
+ function formatOptionalNumber(value) {
67455
+ return value === void 0 ? "?" : String(value);
67456
+ }
67457
+
67458
+ // src/shell/debug-agent-events.ts
67459
+ var TOOL_INPUT_MAX = 120;
67460
+ var TOOL_ERROR_MAX = 120;
67461
+ var UNSERIALIZABLE = "[unserializable]";
67462
+ function tryStringify(value) {
67463
+ const result = JSON.stringify(value);
67464
+ return result ?? UNSERIALIZABLE;
67465
+ }
67466
+ var safeStringify = (0, import_neverthrow35.fromThrowable)(tryStringify, () => ({
67467
+ type: "STRINGIFY_FAILED"
67468
+ }));
67469
+ function toolKey(scope, event) {
67470
+ const prefix = scope === void 0 ? "" : `${scope}:`;
67471
+ return `${prefix}${event.agent}:${event.toolName}`;
67472
+ }
67473
+ function stringifyToolInput(input) {
67474
+ if (input === void 0 || input === null) {
67475
+ return "";
67476
+ }
67477
+ if (typeof input === "string") {
67478
+ return input;
67479
+ }
67480
+ return safeStringify(input).unwrapOr(UNSERIALIZABLE);
67481
+ }
67482
+ function truncate(value, max) {
67483
+ return value.length > max ? `${value.slice(0, max)}\u2026` : value;
67484
+ }
67485
+ function handleStageStart(context, event) {
67486
+ context.logger.log(
67487
+ "STAGE_START",
67488
+ `${event.agent} steps=${formatOptionalNumber(event.totalSteps)}`
67489
+ );
67490
+ }
67491
+ function handleStageEnd(context, event) {
67492
+ context.logger.log("STAGE_END", `${event.agent} duration=${String(event.durationMs)}ms`);
67493
+ }
67494
+ function handleToolCall(context, event) {
67495
+ context.logger.markCall(toolKey(context.scope, event));
67496
+ const input = truncate(stringifyToolInput(event.input), TOOL_INPUT_MAX);
67497
+ context.logger.log("TOOL_CALL", `${event.agent} ${event.toolName} ${input}`);
67498
+ }
67499
+ function handleToolResult(context, event) {
67500
+ const suffix = formatDurationSuffix(context.logger.elapsedSince(toolKey(context.scope, event)));
67501
+ context.logger.log("TOOL_RESULT", `${event.agent} ${event.toolName}${suffix}`);
67502
+ }
67503
+ function handleToolError(context, event) {
67504
+ const suffix = formatDurationSuffix(context.logger.elapsedSince(toolKey(context.scope, event)));
67505
+ const error48 = truncate(event.error, TOOL_ERROR_MAX);
67506
+ context.logger.log("TOOL_ERROR", `${event.agent} ${event.toolName}${suffix} ${error48}`);
67507
+ }
67508
+ function handleAgentRetry(context, event) {
67509
+ context.logger.log(
67510
+ "AGENT_RETRY",
67511
+ `${event.agent} attempt=${String(event.attempt)}/${String(event.maxAttempts)} delay=${String(event.delayMs)}ms`
67512
+ );
67513
+ }
67514
+ function handleAgentFailed(context, event) {
67515
+ context.logger.log(
67516
+ "AGENT_FAILED_NON_CRITICAL",
67517
+ `${event.agent} attempts=${String(event.attempts)}`
67518
+ );
67519
+ }
67520
+ function handleTimeoutGrace(context, event) {
67521
+ context.logger.log(
67522
+ "TIMEOUT_GRACE_ENTERED",
67523
+ `${event.agent} grace=${String(event.gracePeriodMs)}ms`
67524
+ );
67525
+ }
67526
+ function handleInspectorStepStart(context, event) {
67527
+ context.logger.log(
67528
+ "INSPECTOR_STEP_START",
67529
+ `step=${String(event.stepIndex)}/${formatOptionalNumber(event.totalSteps)} screen=${event.screenLabel}`
67530
+ );
67531
+ }
67532
+ function handleInspectorStep(context, event) {
67533
+ context.logger.log(
67534
+ "INSPECTOR_STEP",
67535
+ `step=${String(event.stepIndex)} screen=${event.screenLabel} outcome=${event.outcome}`
67536
+ );
67537
+ }
67538
+ function handleVisualStep(context, event) {
67539
+ context.logger.log("VISUAL_STEP", `step=${String(event.stepIndex)} screen=${event.screenLabel}`);
67540
+ }
67541
+ function handleError(context, event) {
67542
+ context.logger.log("ERROR", `${event.agent} ${event.message}`);
67543
+ }
67544
+ var AGENT_EVENT_HANDLERS = {
67545
+ STAGE_START: handleStageStart,
67546
+ STAGE_END: handleStageEnd,
67547
+ TOOL_CALL: handleToolCall,
67548
+ TOOL_RESULT: handleToolResult,
67549
+ TOOL_ERROR: handleToolError,
67550
+ AGENT_RETRY: handleAgentRetry,
67551
+ AGENT_FAILED_NON_CRITICAL: handleAgentFailed,
67552
+ TIMEOUT_GRACE_ENTERED: handleTimeoutGrace,
67553
+ INSPECTOR_STEP_START: handleInspectorStepStart,
67554
+ INSPECTOR_STEP: handleInspectorStep,
67555
+ VISUAL_STEP: handleVisualStep,
67556
+ ERROR: handleError
67557
+ };
67558
+ function handleAgentEvent(context, event) {
67559
+ const handler = AGENT_EVENT_HANDLERS[event.type];
67560
+ if (handler === void 0) {
67561
+ return;
67562
+ }
67563
+ handler(context, event);
67564
+ }
67565
+
67566
+ // src/shell/debug-suite-events.ts
67567
+ function handleItemQueued(logger, event) {
67568
+ logger.log("ITEM_QUEUED", `id=${event.item.id} name=${event.item.name}`);
67569
+ }
67570
+ function handleItemStarted(logger, event) {
67571
+ logger.log(
67572
+ "ITEM_STARTED",
67573
+ `id=${event.itemId} name=${event.itemName} sim=${event.simulatorUdid}`
67574
+ );
67575
+ }
67576
+ function handleItemCompleted(logger, event) {
67577
+ logger.log(
67578
+ "ITEM_COMPLETED",
67579
+ `id=${event.itemId} findings=${String(event.findingsCount)} duration=${String(event.durationMs)}ms`
67580
+ );
67581
+ }
67582
+ function handleItemFailed(logger, event) {
67583
+ logger.log(
67584
+ "ITEM_FAILED",
67585
+ `id=${event.itemId} error=${event.error} duration=${String(event.durationMs)}ms`
67586
+ );
67587
+ }
67588
+ function handleItemTimeout(logger, event) {
67589
+ logger.log("ITEM_TIMEOUT", `id=${event.itemId} duration=${String(event.durationMs)}ms`);
67590
+ }
67591
+ function handleSimulatorUnhealthy(logger, event) {
67592
+ logger.log(
67593
+ "SIMULATOR_UNHEALTHY",
67594
+ `sim=${event.simulatorUdid} failures=${String(event.consecutiveFailures)}`
67595
+ );
67596
+ }
67597
+ function handleSuiteCompleted(logger, event) {
67598
+ logger.log(
67599
+ "SUITE_COMPLETED",
67600
+ `items=${String(event.totalItems)} findings=${String(event.totalFindings)} duration=${String(event.durationMs)}ms aborted=${String(event.aborted)}`
67601
+ );
67602
+ }
67603
+ var SUITE_EVENT_HANDLERS = {
67604
+ ITEM_QUEUED: handleItemQueued,
67605
+ ITEM_STARTED: handleItemStarted,
67606
+ ITEM_COMPLETED: handleItemCompleted,
67607
+ ITEM_FAILED: handleItemFailed,
67608
+ ITEM_TIMEOUT: handleItemTimeout,
67609
+ SIMULATOR_UNHEALTHY: handleSimulatorUnhealthy,
67610
+ SUITE_COMPLETED: handleSuiteCompleted
67611
+ };
67612
+ function handleSuiteEvent(logger, event) {
67613
+ const handler = SUITE_EVENT_HANDLERS[event.type];
67614
+ if (handler === void 0) {
67615
+ return;
67616
+ }
67617
+ handler(logger, event);
67618
+ }
67619
+
67620
+ // src/shell/debug-logger.ts
67621
+ function passthroughAgent(handler) {
67622
+ return handler;
67623
+ }
67624
+ function passthroughSuite(handler) {
67625
+ return handler;
67626
+ }
67627
+ function createDebugLogger(options) {
67628
+ if (options.enabled !== true) {
67629
+ return {
67630
+ wrapAgentHandler: passthroughAgent,
67631
+ wrapSuiteHandler: passthroughSuite
67632
+ };
67633
+ }
67634
+ const logger = buildLogger(options);
67635
+ return {
67636
+ wrapAgentHandler: (handler, scope) => (event) => {
67637
+ handleAgentEvent({ logger, scope }, event);
67638
+ handler(event);
67639
+ },
67640
+ wrapSuiteHandler: (handler) => (event) => {
67641
+ handleSuiteEvent(logger, event);
67642
+ handler(event);
67643
+ }
67644
+ };
67645
+ }
67646
+
67319
67647
  // src/shell/trigger-abort.ts
67320
67648
  function triggerAbort() {
67321
67649
  process.kill(process.pid, "SIGINT");
@@ -67356,7 +67684,7 @@ function buildArtifacts(videoPath) {
67356
67684
  return { videoPath, videoPath2x: "", videoPath4x: videoPath, findings: [], snapshots: [] };
67357
67685
  }
67358
67686
  async function checkVideoPathExists(videoPath) {
67359
- const safeAccess = (0, import_neverthrow35.fromAsyncThrowable)(import_promises16.access, () => ({ type: "FILE_NOT_FOUND" }));
67687
+ const safeAccess = (0, import_neverthrow36.fromAsyncThrowable)(import_promises17.access, () => ({ type: "FILE_NOT_FOUND" }));
67360
67688
  const result = await safeAccess(videoPath);
67361
67689
  return result.isOk();
67362
67690
  }
@@ -67377,20 +67705,32 @@ function handleAnalyseError(state, errorType) {
67377
67705
  `);
67378
67706
  process.exit(1);
67379
67707
  }
67380
- async function runAnalysisAndExit(artifacts, apiKey) {
67381
- const identity = {
67708
+ function buildAnalyseIdentity() {
67709
+ return {
67382
67710
  display: createSoloDisplay(),
67383
67711
  itemId: ITEM_ID,
67384
67712
  itemName: ITEM_NAME,
67385
67713
  simulatorUdid: ""
67386
67714
  };
67387
- const startedAt = Date.now();
67388
- emitItemStart(identity, startedAt);
67389
- const onEvent = (event) => {
67715
+ }
67716
+ function buildAnalyseEventHandler(identity, debug) {
67717
+ const debugLogger = createDebugLogger({ enabled: debug });
67718
+ const baseHandler = (event) => {
67390
67719
  identity.display.onEvent({ ...event, itemId: ITEM_ID });
67391
67720
  };
67721
+ return debugLogger.wrapAgentHandler(baseHandler);
67722
+ }
67723
+ async function runAnalysisAndExit(input) {
67724
+ const identity = buildAnalyseIdentity();
67725
+ const startedAt = Date.now();
67726
+ emitItemStart(identity, startedAt);
67727
+ const onEvent = buildAnalyseEventHandler(identity, input.debug);
67392
67728
  const state = { identity, startedAt };
67393
- const result = await runAnalysis(artifacts, { apiKey, videoVariant: "1x", onEvent });
67729
+ const result = await runAnalysis(input.artifacts, {
67730
+ apiKey: input.apiKey,
67731
+ videoVariant: "1x",
67732
+ onEvent
67733
+ });
67394
67734
  result.match(
67395
67735
  (findings) => {
67396
67736
  handleAnalyseSuccess(state, findings);
@@ -67400,13 +67740,16 @@ async function runAnalysisAndExit(artifacts, apiKey) {
67400
67740
  }
67401
67741
  );
67402
67742
  }
67403
- async function runAnalyseCommand(videoPath, config3) {
67743
+ function ensureApiKey(config3) {
67404
67744
  const apiKey = config3.GOOGLE_GENERATIVE_AI_API_KEY ?? "";
67405
67745
  if (!apiKey) {
67406
67746
  process.stderr.write("GOOGLE_GENERATIVE_AI_API_KEY is not set\n");
67407
67747
  process.exit(1);
67408
67748
  return;
67409
67749
  }
67750
+ return apiKey;
67751
+ }
67752
+ async function resolveVideoPath2(videoPath) {
67410
67753
  if (videoPath === void 0) {
67411
67754
  process.stderr.write('A video path is required. Pass the path printed by "xqa explore".\n');
67412
67755
  process.exit(1);
@@ -67418,11 +67761,26 @@ async function runAnalyseCommand(videoPath, config3) {
67418
67761
  process.exit(1);
67419
67762
  return;
67420
67763
  }
67421
- await runAnalysisAndExit(buildArtifacts(videoPath), apiKey);
67764
+ return videoPath;
67765
+ }
67766
+ async function runAnalyseCommand(input) {
67767
+ const apiKey = ensureApiKey(input.config);
67768
+ if (apiKey === void 0) {
67769
+ return;
67770
+ }
67771
+ const videoPath = await resolveVideoPath2(input.videoPath);
67772
+ if (videoPath === void 0) {
67773
+ return;
67774
+ }
67775
+ await runAnalysisAndExit({
67776
+ artifacts: buildArtifacts(videoPath),
67777
+ apiKey,
67778
+ debug: input.debug
67779
+ });
67422
67780
  }
67423
67781
 
67424
67782
  // src/core/completion-generator.ts
67425
- var import_neverthrow36 = __toESM(require_index_cjs(), 1);
67783
+ var import_neverthrow37 = __toESM(require_index_cjs(), 1);
67426
67784
  function extractLongFlags(flags) {
67427
67785
  return flags.split(/[\s,]+/).filter((token) => token.startsWith("--"));
67428
67786
  }
@@ -67512,9 +67870,9 @@ complete -F _xqa_completion xqa`;
67512
67870
  }
67513
67871
  function generateCompletion(commands, shell) {
67514
67872
  if (shell !== "bash" && shell !== "zsh") {
67515
- return (0, import_neverthrow36.err)({ type: "UNSUPPORTED_SHELL", shell });
67873
+ return (0, import_neverthrow37.err)({ type: "UNSUPPORTED_SHELL", shell });
67516
67874
  }
67517
- return (0, import_neverthrow36.ok)(shell === "zsh" ? generateZshCompletion(commands) : generateBashCompletion(commands));
67875
+ return (0, import_neverthrow37.ok)(shell === "zsh" ? generateZshCompletion(commands) : generateBashCompletion(commands));
67518
67876
  }
67519
67877
 
67520
67878
  // src/commands/completion-command.ts
@@ -67549,39 +67907,19 @@ function runCompletionCommand(program3, shell) {
67549
67907
  // src/commands/explore-command.ts
67550
67908
  var import_node_path10 = __toESM(require("node:path"), 1);
67551
67909
 
67552
- // src/constants.ts
67553
- var MOBILE_IOS_TOOLS = [
67554
- "mcp__mobile-ios__tap",
67555
- "mcp__mobile-ios__double_tap",
67556
- "mcp__mobile-ios__long_press",
67557
- "mcp__mobile-ios__swipe",
67558
- "mcp__mobile-ios__type_text",
67559
- "mcp__mobile-ios__press_button",
67560
- "mcp__mobile-ios__launch_app",
67561
- "mcp__mobile-ios__terminate_app",
67562
- "mcp__mobile-ios__list_apps",
67563
- "mcp__mobile-ios__screenshot",
67564
- "mcp__mobile-ios__accessibility_snapshot"
67565
- ];
67566
- var ALLOWED_TOOLS = [...MOBILE_IOS_TOOLS];
67567
- var MS_PER_SECOND3 = 1e3;
67568
- var DEFAULT_ABORT_EXIT_CODE = 130;
67569
- var IDB_INSTALL_MESSAGE = "idb is not installed. Run:\n brew install idb-companion\n pipx install fb-idb\n";
67570
- var FFMPEG_INSTALL_MESSAGE = "ffmpeg is not installed. Run:\n brew install ffmpeg\n";
67571
-
67572
67910
  // src/core/last-path.ts
67573
67911
  var import_node_fs4 = require("node:fs");
67574
67912
  var import_node_path8 = __toESM(require("node:path"), 1);
67575
- var import_neverthrow37 = __toESM(require_index_cjs(), 1);
67913
+ var import_neverthrow38 = __toESM(require_index_cjs(), 1);
67576
67914
  function resolveLastPath(argument, stateContent) {
67577
67915
  if (argument !== void 0) {
67578
- return (0, import_neverthrow37.ok)(argument);
67916
+ return (0, import_neverthrow38.ok)(argument);
67579
67917
  }
67580
67918
  const trimmed = stateContent?.trim();
67581
67919
  if (trimmed) {
67582
- return (0, import_neverthrow37.ok)(trimmed);
67920
+ return (0, import_neverthrow38.ok)(trimmed);
67583
67921
  }
67584
- return (0, import_neverthrow37.err)({ type: "NO_ARG_AND_NO_STATE" });
67922
+ return (0, import_neverthrow38.err)({ type: "NO_ARG_AND_NO_STATE" });
67585
67923
  }
67586
67924
  function lastPathFilePath(xqaDirectoryectory) {
67587
67925
  return import_node_path8.default.join(xqaDirectoryectory, "last-findings-path");
@@ -67591,9 +67929,9 @@ function writeLastPath(xqaDirectory, findingsPath) {
67591
67929
  }
67592
67930
 
67593
67931
  // src/shell/app-context.ts
67594
- var import_promises17 = require("node:fs/promises");
67932
+ var import_promises18 = require("node:fs/promises");
67595
67933
  var import_node_path9 = __toESM(require("node:path"), 1);
67596
- var import_neverthrow38 = __toESM(require_index_cjs(), 1);
67934
+ var import_neverthrow39 = __toESM(require_index_cjs(), 1);
67597
67935
  var HTML_COMMENT_PATTERN = /<!--[\s\S]*?-->/g;
67598
67936
  function isEnoentError(value) {
67599
67937
  return value !== null && typeof value === "object" && "code" in value && value.code === "ENOENT";
@@ -67603,10 +67941,10 @@ function toAppContextError(cause) {
67603
67941
  }
67604
67942
  function absentContext() {
67605
67943
  const absent = void 0;
67606
- return (0, import_neverthrow38.ok)(absent);
67944
+ return (0, import_neverthrow39.ok)(absent);
67607
67945
  }
67608
- var safeReadFile2 = import_neverthrow38.ResultAsync.fromThrowable(
67609
- async (filePath) => (0, import_promises17.readFile)(filePath, "utf8"),
67946
+ var safeReadFile2 = import_neverthrow39.ResultAsync.fromThrowable(
67947
+ async (filePath) => (0, import_promises18.readFile)(filePath, "utf8"),
67610
67948
  toAppContextError
67611
67949
  );
67612
67950
  function stripAndNormalize(content) {
@@ -67619,7 +67957,7 @@ function readContextFile(xqaDirectory, filename) {
67619
67957
  if (isEnoentError(error48.cause)) {
67620
67958
  return absentContext();
67621
67959
  }
67622
- return (0, import_neverthrow38.err)(error48);
67960
+ return (0, import_neverthrow39.err)(error48);
67623
67961
  });
67624
67962
  }
67625
67963
  function readAppContext(xqaDirectory) {
@@ -67631,7 +67969,7 @@ function readExploreContext(xqaDirectory) {
67631
67969
 
67632
67970
  // src/shell/ffmpeg-check.ts
67633
67971
  var import_node_child_process5 = require("node:child_process");
67634
- var import_neverthrow39 = __toESM(require_index_cjs(), 1);
67972
+ var import_neverthrow40 = __toESM(require_index_cjs(), 1);
67635
67973
  async function whichFfmpeg() {
67636
67974
  const { promise: promise2, resolve, reject } = Promise.withResolvers();
67637
67975
  (0, import_node_child_process5.execFile)("which", ["ffmpeg"], (error48, stdout) => {
@@ -67643,7 +67981,7 @@ async function whichFfmpeg() {
67643
67981
  });
67644
67982
  return promise2;
67645
67983
  }
67646
- var safeWhichFfmpeg = (0, import_neverthrow39.fromAsyncThrowable)(
67984
+ var safeWhichFfmpeg = (0, import_neverthrow40.fromAsyncThrowable)(
67647
67985
  whichFfmpeg,
67648
67986
  () => ({ type: "FFMPEG_NOT_FOUND" })
67649
67987
  );
@@ -67680,7 +68018,7 @@ function buildExplorerConfig2({
67680
68018
  mode: "freestyle",
67681
68019
  mcpServers: createDefaultMcpServers(),
67682
68020
  allowedTools: ALLOWED_TOOLS,
67683
- timeoutMs: timeoutSeconds === void 0 ? void 0 : timeoutSeconds * MS_PER_SECOND3,
68021
+ timeoutMs: timeoutSeconds === void 0 ? void 0 : timeoutSeconds * MS_PER_SECOND4,
67684
68022
  appContext,
67685
68023
  initialState: resolvedStartingState,
67686
68024
  buildEnv: config3.QA_BUILD_ENV
@@ -67781,9 +68119,11 @@ function buildExploreRunState({
67781
68119
  simulatorUdid
67782
68120
  }) {
67783
68121
  const { config: config3, xqaDirectory } = options;
67784
- const onEvent = (event) => {
68122
+ const debugLogger = createDebugLogger({ enabled: input.debug ?? false });
68123
+ const baseHandler = (event) => {
67785
68124
  identity.display.onEvent({ ...event, itemId: ITEM_ID2 });
67786
68125
  };
68126
+ const onEvent = debugLogger.wrapAgentHandler(baseHandler);
67787
68127
  const onSuccess = buildSuccessHandler({ input, xqaDirectory, identity, startedAt });
67788
68128
  const onError = buildErrorHandler(identity, startedAt);
67789
68129
  return { input, config: config3, xqaDirectory, onEvent, onSuccess, onError, simulatorUdid };
@@ -67932,7 +68272,7 @@ function runInitCommand() {
67932
68272
  // src/commands/review-command.ts
67933
68273
  var import_node_fs7 = require("node:fs");
67934
68274
  var import_node_path14 = __toESM(require("node:path"), 1);
67935
- var import_neverthrow41 = __toESM(require_index_cjs(), 1);
68275
+ var import_neverthrow42 = __toESM(require_index_cjs(), 1);
67936
68276
 
67937
68277
  // ../../node_modules/.pnpm/@inquirer+core@10.3.2_@types+node@22.19.15/node_modules/@inquirer/core/dist/esm/lib/key.js
67938
68278
  var isUpKey = (key, keybindings = []) => (
@@ -69314,9 +69654,9 @@ var RemoveFileError = class extends Error {
69314
69654
  // ../../node_modules/.pnpm/@inquirer+external-editor@1.0.3_@types+node@22.19.15/node_modules/@inquirer/external-editor/dist/esm/index.js
69315
69655
  function editAsync(text = "", callback, fileOptions) {
69316
69656
  const editor = new ExternalEditor(text, fileOptions);
69317
- editor.runAsync((err18, result) => {
69318
- if (err18) {
69319
- setImmediate(callback, err18, void 0);
69657
+ editor.runAsync((err19, result) => {
69658
+ if (err19) {
69659
+ setImmediate(callback, err19, void 0);
69320
69660
  } else {
69321
69661
  try {
69322
69662
  editor.cleanup();
@@ -70337,7 +70677,7 @@ var esm_default11 = createPrompt((config3, done) => {
70337
70677
  });
70338
70678
 
70339
70679
  // src/review-session.ts
70340
- var import_neverthrow40 = __toESM(require_index_cjs(), 1);
70680
+ var import_neverthrow41 = __toESM(require_index_cjs(), 1);
70341
70681
  var CONFIDENCE_PERCENT = 100;
70342
70682
  var FLOW_COL_WIDTH = 35;
70343
70683
  var TRIGGER_COL_WIDTH = 16;
@@ -70490,15 +70830,15 @@ async function runInteractiveLoop(findings, existing) {
70490
70830
  }
70491
70831
  return { staged: state.staged, undoneKeys: state.undoneKeys };
70492
70832
  }
70493
- var safeRunInteractiveLoop = (0, import_neverthrow40.fromAsyncThrowable)(
70833
+ var safeRunInteractiveLoop = (0, import_neverthrow41.fromAsyncThrowable)(
70494
70834
  runInteractiveLoop,
70495
70835
  (error48) => error48 instanceof Error && error48.name === "ExitPromptError" ? "exit-prompt" : "unexpected"
70496
70836
  );
70497
70837
 
70498
70838
  // src/commands/review-command.ts
70499
- var safeReadFile3 = (0, import_neverthrow41.fromThrowable)((filePath) => (0, import_node_fs7.readFileSync)(filePath, "utf8"));
70500
- var safeParseJson3 = (0, import_neverthrow41.fromThrowable)(JSON.parse);
70501
- var safeWrite = (0, import_neverthrow41.fromThrowable)((filePath, content) => {
70839
+ var safeReadFile3 = (0, import_neverthrow42.fromThrowable)((filePath) => (0, import_node_fs7.readFileSync)(filePath, "utf8"));
70840
+ var safeParseJson3 = (0, import_neverthrow42.fromThrowable)(JSON.parse);
70841
+ var safeWrite = (0, import_neverthrow42.fromThrowable)((filePath, content) => {
70502
70842
  (0, import_node_fs7.writeFileSync)(filePath, content);
70503
70843
  });
70504
70844
  function readLastPath(xqaDirectory) {
@@ -70515,13 +70855,13 @@ function isPipelineOutput(data) {
70515
70855
  function readFindings(filePath) {
70516
70856
  const readResult = safeReadFile3(filePath);
70517
70857
  if (readResult.isErr()) {
70518
- return (0, import_neverthrow41.err)("not-found");
70858
+ return (0, import_neverthrow42.err)("not-found");
70519
70859
  }
70520
70860
  return safeParseJson3(readResult.value).mapErr(() => "invalid").andThen((data) => {
70521
70861
  if (!isPipelineOutput(data)) {
70522
- return (0, import_neverthrow41.err)("invalid");
70862
+ return (0, import_neverthrow42.err)("invalid");
70523
70863
  }
70524
- return (0, import_neverthrow41.ok)(data);
70864
+ return (0, import_neverthrow42.ok)(data);
70525
70865
  });
70526
70866
  }
70527
70867
  function loadExistingDismissals(filePath) {
@@ -70594,7 +70934,7 @@ function resolveAndReadFindings(findingsPath, xqaDirectory) {
70594
70934
  "No findings path provided and no last path found. Run: xqa review <findings-path>\n"
70595
70935
  );
70596
70936
  process.exit(1);
70597
- return (0, import_neverthrow41.err)();
70937
+ return (0, import_neverthrow42.err)();
70598
70938
  }
70599
70939
  const resolvedPath = resolvedPathResult.value;
70600
70940
  const findingsResult = readFindings(resolvedPath);
@@ -70607,9 +70947,9 @@ function resolveAndReadFindings(findingsPath, xqaDirectory) {
70607
70947
  `);
70608
70948
  }
70609
70949
  process.exit(1);
70610
- return (0, import_neverthrow41.err)();
70950
+ return (0, import_neverthrow42.err)();
70611
70951
  }
70612
- return (0, import_neverthrow41.ok)({ resolvedPath, output: findingsResult.value });
70952
+ return (0, import_neverthrow42.ok)({ resolvedPath, output: findingsResult.value });
70613
70953
  }
70614
70954
  async function runReviewLoop({
70615
70955
  findings,
@@ -70683,40 +71023,40 @@ function stripExtensions(filename) {
70683
71023
  // src/commands/spec-resolver.ts
70684
71024
  var import_node_fs8 = require("node:fs");
70685
71025
  var import_node_path16 = __toESM(require("node:path"), 1);
70686
- var import_neverthrow43 = __toESM(require_index_cjs(), 1);
71026
+ var import_neverthrow44 = __toESM(require_index_cjs(), 1);
70687
71027
 
70688
71028
  // src/spec-frontmatter.ts
70689
- var import_neverthrow42 = __toESM(require_index_cjs(), 1);
71029
+ var import_neverthrow43 = __toESM(require_index_cjs(), 1);
70690
71030
  var FRONTMATTER_OPEN_LEN = 4;
70691
71031
  var FRONTMATTER_MARKER_LEN = 3;
70692
71032
  function extractFrontmatterBlock(content) {
70693
71033
  const normalized = content.replaceAll("\r\n", "\n");
70694
71034
  if (!normalized.startsWith("---")) {
70695
- return (0, import_neverthrow42.err)({ type: "MISSING_FRONTMATTER" });
71035
+ return (0, import_neverthrow43.err)({ type: "MISSING_FRONTMATTER" });
70696
71036
  }
70697
71037
  const end = normalized.indexOf("\n---", FRONTMATTER_MARKER_LEN);
70698
71038
  if (end === -1) {
70699
- return (0, import_neverthrow42.err)({ type: "MISSING_FRONTMATTER" });
71039
+ return (0, import_neverthrow43.err)({ type: "MISSING_FRONTMATTER" });
70700
71040
  }
70701
- return (0, import_neverthrow42.ok)(normalized.slice(FRONTMATTER_OPEN_LEN, end));
71041
+ return (0, import_neverthrow43.ok)(normalized.slice(FRONTMATTER_OPEN_LEN, end));
70702
71042
  }
70703
71043
  function parseTimeout(fields) {
70704
71044
  const raw = fields.get("timeout");
70705
71045
  if (raw === void 0) {
70706
- return (0, import_neverthrow42.ok)(raw);
71046
+ return (0, import_neverthrow43.ok)(raw);
70707
71047
  }
70708
71048
  const parsed = Number(raw);
70709
71049
  if (Number.isNaN(parsed) || parsed <= 0) {
70710
- return (0, import_neverthrow42.err)({ type: "PARSE_ERROR", cause: `invalid timeout: ${raw}` });
71050
+ return (0, import_neverthrow43.err)({ type: "PARSE_ERROR", cause: `invalid timeout: ${raw}` });
70711
71051
  }
70712
- return (0, import_neverthrow42.ok)(parsed);
71052
+ return (0, import_neverthrow43.ok)(parsed);
70713
71053
  }
70714
71054
  function parseSpecFrontmatter(content) {
70715
71055
  return extractFrontmatterBlock(content).andThen((block) => {
70716
71056
  const fields = parseYamlFields(block);
70717
71057
  const feature = fields.get("feature");
70718
71058
  if (feature === void 0) {
70719
- return (0, import_neverthrow42.err)({ type: "MISSING_FIELD", field: "feature" });
71059
+ return (0, import_neverthrow43.err)({ type: "MISSING_FIELD", field: "feature" });
70720
71060
  }
70721
71061
  return parseTimeout(fields).map((timeout) => ({ feature, timeout }));
70722
71062
  });
@@ -70738,12 +71078,12 @@ function parseYamlFields(block) {
70738
71078
  }
70739
71079
 
70740
71080
  // src/commands/spec-resolver.ts
70741
- var safeReadFile4 = (0, import_neverthrow43.fromThrowable)((filePath) => (0, import_node_fs8.readFileSync)(filePath, "utf8"));
70742
- var safeReaddir = (0, import_neverthrow43.fromThrowable)(
71081
+ var safeReadFile4 = (0, import_neverthrow44.fromThrowable)((filePath) => (0, import_node_fs8.readFileSync)(filePath, "utf8"));
71082
+ var safeReaddir = (0, import_neverthrow44.fromThrowable)(
70743
71083
  (directory) => (0, import_node_fs8.readdirSync)(directory, { recursive: true, encoding: "utf8" })
70744
71084
  );
70745
71085
  var CANCEL = "xqa:cancel";
70746
- var safeSelect = import_neverthrow43.ResultAsync.fromThrowable(
71086
+ var safeSelect = import_neverthrow44.ResultAsync.fromThrowable(
70747
71087
  esm_default11,
70748
71088
  (error48) => error48 instanceof Error && error48.name === "ExitPromptError" ? "cancelled" : "failed"
70749
71089
  );
@@ -70808,7 +71148,7 @@ function buildSpecExplorer(input, context) {
70808
71148
  specFiles: [context.absolutePath],
70809
71149
  mcpServers: createDefaultMcpServers(),
70810
71150
  allowedTools: ALLOWED_TOOLS,
70811
- timeoutMs: context.timeout === void 0 ? void 0 : context.timeout * MS_PER_SECOND3,
71151
+ timeoutMs: context.timeout === void 0 ? void 0 : context.timeout * MS_PER_SECOND4,
70812
71152
  appContext: context.appContext,
70813
71153
  buildEnv: context.config.QA_BUILD_ENV
70814
71154
  };
@@ -70867,9 +71207,11 @@ async function executeSpec(input, context) {
70867
71207
  };
70868
71208
  const startedAt = Date.now();
70869
71209
  emitItemStart(identity, startedAt);
70870
- const onEvent = (event) => {
71210
+ const debugLogger = createDebugLogger({ enabled: input.debug ?? false });
71211
+ const baseHandler = (event) => {
70871
71212
  identity.display.onEvent({ ...event, itemId: identity.itemId });
70872
71213
  };
71214
+ const onEvent = debugLogger.wrapAgentHandler(baseHandler);
70873
71215
  const { onSuccess, onError } = handleSpecResult({ identity, startedAt }, context);
70874
71216
  const result = await runPipeline2(buildPipelineConfig2({ input, context, onEvent }));
70875
71217
  result.match(onSuccess, onError);
@@ -70931,7 +71273,7 @@ function runUpdateCommand() {
70931
71273
  var import_node_path18 = __toESM(require("node:path"), 1);
70932
71274
  var import_node_url2 = require("node:url");
70933
71275
  var import_dotenv = __toESM(require_main(), 1);
70934
- var import_neverthrow44 = __toESM(require_index_cjs(), 1);
71276
+ var import_neverthrow45 = __toESM(require_index_cjs(), 1);
70935
71277
 
70936
71278
  // ../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/external.js
70937
71279
  var external_exports2 = {};
@@ -71671,8 +72013,8 @@ var ZodType2 = class {
71671
72013
  } : {
71672
72014
  issues: ctx.common.issues
71673
72015
  };
71674
- } catch (err18) {
71675
- if (err18?.message?.toLowerCase()?.includes("encountered")) {
72016
+ } catch (err19) {
72017
+ if (err19?.message?.toLowerCase()?.includes("encountered")) {
71676
72018
  this["~standard"].async = true;
71677
72019
  }
71678
72020
  ctx.common = {
@@ -74992,10 +75334,10 @@ function loadConfig() {
74992
75334
  const messages = result.error.issues.map(
74993
75335
  (issue2) => ` - ${issue2.path.join(".")}: ${issue2.message}`
74994
75336
  );
74995
- return (0, import_neverthrow44.err)({ type: "INVALID_CONFIG", message: `Configuration error:
75337
+ return (0, import_neverthrow45.err)({ type: "INVALID_CONFIG", message: `Configuration error:
74996
75338
  ${messages.join("\n")}` });
74997
75339
  }
74998
- return (0, import_neverthrow44.ok)(result.data);
75340
+ return (0, import_neverthrow45.ok)(result.data);
74999
75341
  }
75000
75342
 
75001
75343
  // src/core/parse-timeout-seconds.ts
@@ -75028,7 +75370,7 @@ function parseVerboseOption(value) {
75028
75370
 
75029
75371
  // src/pid-lock.ts
75030
75372
  var import_node_fs9 = require("node:fs");
75031
- var import_neverthrow45 = __toESM(require_index_cjs(), 1);
75373
+ var import_neverthrow46 = __toESM(require_index_cjs(), 1);
75032
75374
  var PID_FILE = "/tmp/xqa.pid";
75033
75375
  var SIGINT_EXIT_CODE = 130;
75034
75376
  var SIGTERM_EXIT_CODE = 143;
@@ -75037,7 +75379,7 @@ var HARD_TIMEOUT_MS = 1e4;
75037
75379
  var cleanup = () => {
75038
75380
  (0, import_node_fs9.rmSync)(PID_FILE, { force: true });
75039
75381
  };
75040
- var checkProcessRunning = (0, import_neverthrow45.fromThrowable)(
75382
+ var checkProcessRunning = (0, import_neverthrow46.fromThrowable)(
75041
75383
  (pid) => {
75042
75384
  process.kill(pid, 0);
75043
75385
  return true;
@@ -75103,27 +75445,27 @@ function acquireLock() {
75103
75445
  // src/shell/xqa-directory.ts
75104
75446
  var import_node_fs10 = require("node:fs");
75105
75447
  var import_node_path19 = __toESM(require("node:path"), 1);
75106
- var import_neverthrow46 = __toESM(require_index_cjs(), 1);
75448
+ var import_neverthrow47 = __toESM(require_index_cjs(), 1);
75107
75449
  function findXqaDirectory(startDirectory) {
75108
75450
  let current = startDirectory;
75109
75451
  for (; ; ) {
75110
75452
  const candidate = import_node_path19.default.join(current, ".xqa");
75111
75453
  if ((0, import_node_fs10.existsSync)(candidate)) {
75112
- return (0, import_neverthrow46.ok)(candidate);
75454
+ return (0, import_neverthrow47.ok)(candidate);
75113
75455
  }
75114
75456
  const parent = import_node_path19.default.dirname(current);
75115
75457
  if (parent === current) {
75116
- return (0, import_neverthrow46.err)({ type: "XQA_NOT_INITIALIZED" });
75458
+ return (0, import_neverthrow47.err)({ type: "XQA_NOT_INITIALIZED" });
75117
75459
  }
75118
75460
  current = parent;
75119
75461
  }
75120
75462
  }
75121
75463
 
75122
75464
  // src/suite/commands/run-command.ts
75123
- var import_promises19 = __toESM(require("node:fs/promises"), 1);
75465
+ var import_promises20 = __toESM(require("node:fs/promises"), 1);
75124
75466
  var import_node_path22 = __toESM(require("node:path"), 1);
75125
75467
  var import_fast_glob = __toESM(require_out4(), 1);
75126
- var import_neverthrow51 = __toESM(require_index_cjs(), 1);
75468
+ var import_neverthrow52 = __toESM(require_index_cjs(), 1);
75127
75469
 
75128
75470
  // src/suite/core/run-id.ts
75129
75471
  var RUN_ID_PAD_LENGTH2 = 4;
@@ -75146,8 +75488,8 @@ function computeNextRunId(existingDirectories) {
75146
75488
  }
75147
75489
 
75148
75490
  // src/suite/core/suite-config-parser.ts
75149
- var import_neverthrow47 = __toESM(require_index_cjs(), 1);
75150
75491
  var import_neverthrow48 = __toESM(require_index_cjs(), 1);
75492
+ var import_neverthrow49 = __toESM(require_index_cjs(), 1);
75151
75493
  var freestyleEntrySchema = external_exports2.object({
75152
75494
  prompt: external_exports2.string().optional(),
75153
75495
  timeoutSeconds: external_exports2.number().int().positive()
@@ -75166,7 +75508,7 @@ var suiteConfigSchema = external_exports2.object({
75166
75508
  specs: external_exports2.array(external_exports2.string()).nonempty(),
75167
75509
  freestyle: freestyleSchema
75168
75510
  });
75169
- var safeJsonParse4 = (0, import_neverthrow47.fromThrowable)(
75511
+ var safeJsonParse4 = (0, import_neverthrow48.fromThrowable)(
75170
75512
  JSON.parse,
75171
75513
  (cause) => ({
75172
75514
  type: "INVALID_SUITE_CONFIG",
@@ -75177,9 +75519,9 @@ function parseSuiteConfig(raw) {
75177
75519
  return safeJsonParse4(raw).andThen((data) => {
75178
75520
  const parsed = suiteConfigSchema.safeParse(data);
75179
75521
  if (!parsed.success) {
75180
- return (0, import_neverthrow48.err)({ type: "INVALID_SUITE_CONFIG", cause: parsed.error });
75522
+ return (0, import_neverthrow49.err)({ type: "INVALID_SUITE_CONFIG", cause: parsed.error });
75181
75523
  }
75182
- return (0, import_neverthrow48.ok)({ specs: parsed.data.specs, freestyle: parsed.data.freestyle });
75524
+ return (0, import_neverthrow49.ok)({ specs: parsed.data.specs, freestyle: parsed.data.freestyle });
75183
75525
  });
75184
75526
  }
75185
75527
 
@@ -75243,9 +75585,9 @@ function buildFreestyleItems(entries) {
75243
75585
  }
75244
75586
 
75245
75587
  // src/suite/shell/suite-findings-writer.ts
75246
- var import_promises18 = __toESM(require("node:fs/promises"), 1);
75588
+ var import_promises19 = __toESM(require("node:fs/promises"), 1);
75247
75589
  var import_node_path20 = __toESM(require("node:path"), 1);
75248
- var import_neverthrow49 = __toESM(require_index_cjs(), 1);
75590
+ var import_neverthrow50 = __toESM(require_index_cjs(), 1);
75249
75591
  var INDENT_SPACES = 2;
75250
75592
  function writeSuiteFindings(findings, options) {
75251
75593
  const directory = import_node_path20.default.join(
@@ -75257,11 +75599,11 @@ function writeSuiteFindings(findings, options) {
75257
75599
  );
75258
75600
  const finalPath = import_node_path20.default.join(directory, "findings.json");
75259
75601
  const temporaryPath = `${finalPath}.tmp`;
75260
- const safeWriteAtomically = import_neverthrow49.ResultAsync.fromThrowable(
75602
+ const safeWriteAtomically = import_neverthrow50.ResultAsync.fromThrowable(
75261
75603
  async () => {
75262
- await import_promises18.default.mkdir(directory, { recursive: true });
75263
- await import_promises18.default.writeFile(temporaryPath, JSON.stringify(findings, void 0, INDENT_SPACES));
75264
- await import_promises18.default.rename(temporaryPath, finalPath);
75604
+ await import_promises19.default.mkdir(directory, { recursive: true });
75605
+ await import_promises19.default.writeFile(temporaryPath, JSON.stringify(findings, void 0, INDENT_SPACES));
75606
+ await import_promises19.default.rename(temporaryPath, finalPath);
75265
75607
  return finalPath;
75266
75608
  },
75267
75609
  (cause) => ({ type: "FINDINGS_WRITE_FAILED", cause })
@@ -75270,7 +75612,7 @@ function writeSuiteFindings(findings, options) {
75270
75612
  }
75271
75613
 
75272
75614
  // src/suite/shell/worker-pool.ts
75273
- var import_neverthrow50 = __toESM(require_index_cjs(), 1);
75615
+ var import_neverthrow51 = __toESM(require_index_cjs(), 1);
75274
75616
 
75275
75617
  // src/suite/shell/item-result-builder.ts
75276
75618
  function getErrorMessage(cause) {
@@ -75498,7 +75840,7 @@ function runWorkerPool(config3) {
75498
75840
  const queue = setupQueue(config3);
75499
75841
  const results = [];
75500
75842
  const suiteStartMs = Date.now();
75501
- const safeRun = import_neverthrow50.ResultAsync.fromThrowable(
75843
+ const safeRun = import_neverthrow51.ResultAsync.fromThrowable(
75502
75844
  async () => runAllWorkers({ config: config3, queue, results, suiteStartMs }),
75503
75845
  (cause) => ({ type: "WORKER_POOL_FAILED", cause })
75504
75846
  );
@@ -75538,7 +75880,7 @@ function buildFreestylePipelineConfig(input) {
75538
75880
  date: date5,
75539
75881
  mcpServers: createDefaultMcpServers(simulatorUdid),
75540
75882
  allowedTools: ALLOWED_TOOLS,
75541
- timeoutMs: resolveFreestyleTimeout(item, config3) * MS_PER_SECOND3,
75883
+ timeoutMs: resolveFreestyleTimeout(item, config3) * MS_PER_SECOND4,
75542
75884
  appContext: item.prompt ? `${buildDeviceInstruction(simulatorUdid)}
75543
75885
 
75544
75886
  ${item.prompt}` : buildDeviceInstruction(simulatorUdid),
@@ -75585,15 +75927,15 @@ function makeExecuteItem(context) {
75585
75927
 
75586
75928
  // src/suite/commands/run-command.ts
75587
75929
  var ISO_DATE_LENGTH3 = 10;
75588
- var safeReaddir2 = import_neverthrow51.ResultAsync.fromThrowable(
75930
+ var safeReaddir2 = import_neverthrow52.ResultAsync.fromThrowable(
75589
75931
  async (directoryPath) => {
75590
- const entries = await import_promises19.default.readdir(directoryPath, { withFileTypes: true });
75932
+ const entries = await import_promises20.default.readdir(directoryPath, { withFileTypes: true });
75591
75933
  return entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name);
75592
75934
  },
75593
75935
  () => "READDIR_FAILED"
75594
75936
  );
75595
- var safeReadFile5 = import_neverthrow51.ResultAsync.fromThrowable(
75596
- async (filePath) => import_promises19.default.readFile(filePath, "utf8"),
75937
+ var safeReadFile5 = import_neverthrow52.ResultAsync.fromThrowable(
75938
+ async (filePath) => import_promises20.default.readFile(filePath, "utf8"),
75597
75939
  () => "READ_FAILED"
75598
75940
  );
75599
75941
  async function listRunDirectories(input) {
@@ -75665,17 +76007,21 @@ async function discoverSimulators() {
75665
76007
  return { simulators: result.value, exitCode: 0 };
75666
76008
  }
75667
76009
  async function runPool(input) {
75668
- const { context, items, simulatorUdids, signal } = input;
76010
+ const { context, items, simulatorUdids, debug, signal } = input;
75669
76011
  const display = createSuiteDisplay();
76012
+ const debugLogger = createDebugLogger({ enabled: debug });
76013
+ const baseObserver = (event) => {
76014
+ display.onEvent(event);
76015
+ };
76016
+ const observer = debugLogger.wrapSuiteHandler(baseObserver);
76017
+ const buildItemHandler = (itemId) => debugLogger.wrapAgentHandler((event) => {
76018
+ display.onEvent({ ...event, itemId });
76019
+ }, itemId);
75670
76020
  const poolResult = await runWorkerPool({
75671
76021
  items,
75672
76022
  simulatorUdids,
75673
- observer: (event) => {
75674
- display.onEvent(event);
75675
- },
75676
- onEvent: (itemId) => (event) => {
75677
- display.onEvent({ ...event, itemId });
75678
- },
76023
+ observer,
76024
+ onEvent: buildItemHandler,
75679
76025
  executeItem: makeExecuteItem(context),
75680
76026
  signal
75681
76027
  });
@@ -75713,22 +76059,31 @@ async function buildSuiteRunContext(input) {
75713
76059
  };
75714
76060
  return { suiteId, date: date5, outputDirectory, runId, context };
75715
76061
  }
76062
+ async function executePool(input, runContext) {
76063
+ const { items, simulatorUdids, debug, signal } = input;
76064
+ return runPool({
76065
+ context: runContext.context,
76066
+ items,
76067
+ simulatorUdids,
76068
+ debug,
76069
+ signal
76070
+ });
76071
+ }
75716
76072
  async function runSuite(input) {
75717
- const { items, simulatorUdids, signal } = input;
75718
- const { suiteId, date: date5, outputDirectory, runId, context } = await buildSuiteRunContext(input);
75719
- const poolResult = await runPool({ context, items, simulatorUdids, signal });
76073
+ const runContext = await buildSuiteRunContext(input);
76074
+ const poolResult = await executePool(input, runContext);
75720
76075
  if (poolResult.isErr()) {
75721
76076
  process.stderr.write(`Suite runner failed: ${String(poolResult.error.cause)}
75722
76077
  `);
75723
76078
  return 1;
75724
76079
  }
75725
76080
  return writeAndReport({
75726
- suiteId,
75727
- runId,
75728
- outputDirectory,
75729
- date: date5,
76081
+ suiteId: runContext.suiteId,
76082
+ runId: runContext.runId,
76083
+ outputDirectory: runContext.outputDirectory,
76084
+ date: runContext.date,
75730
76085
  items: poolResult.value,
75731
- aborted: signal.aborted
76086
+ aborted: input.signal.aborted
75732
76087
  });
75733
76088
  }
75734
76089
  async function runSuiteCommand(input) {
@@ -75774,7 +76129,7 @@ function resolveXqaDirectory() {
75774
76129
  return result.value;
75775
76130
  }
75776
76131
  var program2 = new Command();
75777
- program2.name("xqa").description("AI-powered QA agent CLI").version(`${"1.8.0"}${false ? ` (dev build +${"d16dbac"})` : ""}`);
76132
+ program2.name("xqa").description("AI-powered QA agent CLI").version(`${"1.10.0"}${false ? ` (dev build +${"e25bb70"})` : ""}`);
75778
76133
  program2.command("init").description("Initialize a new xqa project in the current directory").action(() => {
75779
76134
  runInitCommand();
75780
76135
  });
@@ -75789,20 +76144,23 @@ program2.command("explore").description("Run the explorer agent; omit prompt for
75789
76144
  "-t, --timeout <seconds>",
75790
76145
  "Explorer timeout in seconds (overrides QA_EXPLORE_TIMEOUT_SECONDS)",
75791
76146
  parseTimeoutSeconds
75792
- ).action((prompt, options) => {
75793
- const xqaDirectory = resolveXqaDirectory();
75794
- runExploreCommand(
75795
- {
75796
- prompt,
75797
- verbose: options.verbose,
75798
- timeoutSeconds: options.timeout,
75799
- signal: controller.signal
75800
- },
75801
- { config: config2, xqaDirectory }
75802
- );
75803
- });
75804
- program2.command("analyse").description("Analyse a session recording with Gemini").argument("[video-path]", "Path to video file").action((videoPath) => {
75805
- void runAnalyseCommand(videoPath, config2);
76147
+ ).option("--debug", "Log timing and event details to stderr").action(
76148
+ (prompt, options) => {
76149
+ const xqaDirectory = resolveXqaDirectory();
76150
+ runExploreCommand(
76151
+ {
76152
+ prompt,
76153
+ verbose: options.verbose,
76154
+ timeoutSeconds: options.timeout,
76155
+ debug: options.debug ?? false,
76156
+ signal: controller.signal
76157
+ },
76158
+ { config: config2, xqaDirectory }
76159
+ );
76160
+ }
76161
+ );
76162
+ program2.command("analyse").description("Analyse a session recording with Gemini").argument("[video-path]", "Path to video file").option("--debug", "Log timing and event details to stderr").action((videoPath, options) => {
76163
+ void runAnalyseCommand({ videoPath, config: config2, debug: options.debug ?? false });
75806
76164
  });
75807
76165
  program2.command("completion").description("Output shell completion script").argument("<shell>", "Shell to generate completion for (bash, zsh)").action((shell) => {
75808
76166
  runCompletionCommand(program2, shell);
@@ -75815,14 +76173,19 @@ program2.command("spec").description("Run the explorer agent against a spec file
75815
76173
  "-v, --verbose [categories]",
75816
76174
  "Verbose output [prompt,tools,screen,memory] (default: all)",
75817
76175
  parseVerboseOption
75818
- ).action((specFile, options) => {
76176
+ ).option("--debug", "Log timing and event details to stderr").action((specFile, options) => {
75819
76177
  const xqaDirectory = resolveXqaDirectory();
75820
76178
  void runSpecCommand(
75821
- { specFile, verbose: options.verbose, signal: controller.signal },
76179
+ {
76180
+ specFile,
76181
+ verbose: options.verbose,
76182
+ debug: options.debug ?? false,
76183
+ signal: controller.signal
76184
+ },
75822
76185
  { config: config2, xqaDirectory }
75823
76186
  );
75824
76187
  });
75825
- program2.command("run").description("Run a test suite or a set of spec files").option("--suite <name>", "Name of the suite to run").option("--spec <globs...>", "Glob patterns matching spec files to run").action(async (options) => {
76188
+ program2.command("run").description("Run a test suite or a set of spec files").option("--suite <name>", "Name of the suite to run").option("--spec <globs...>", "Glob patterns matching spec files to run").option("--debug", "Log timing and event details to stderr").action(async (options) => {
75826
76189
  if (options.suite === void 0 === (options.spec === void 0)) {
75827
76190
  process.stderr.write("Exactly one of --suite or --spec must be provided\n");
75828
76191
  process.exit(1);
@@ -75833,6 +76196,7 @@ program2.command("run").description("Run a test suite or a set of spec files").o
75833
76196
  mode,
75834
76197
  xqaDirectory,
75835
76198
  config: config2,
76199
+ debug: options.debug ?? false,
75836
76200
  signal: controller.signal
75837
76201
  });
75838
76202
  process.exit(exitCode);