@exodus/xqa 1.15.1 → 1.16.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 (3) hide show
  1. package/README.md +2 -0
  2. package/dist/xqa.cjs +611 -538
  3. package/package.json +4 -4
package/dist/xqa.cjs CHANGED
@@ -3539,11 +3539,11 @@ var require_index_cjs = __commonJS({
3539
3539
  try {
3540
3540
  step(g3[n3](v2));
3541
3541
  } catch (e3) {
3542
- settle2(q4[0][3], e3);
3542
+ settle(q4[0][3], e3);
3543
3543
  }
3544
3544
  }
3545
3545
  function step(r3) {
3546
- r3.value instanceof __await2 ? Promise.resolve(r3.value.v).then(fulfill, reject) : settle2(q4[0][2], r3);
3546
+ r3.value instanceof __await2 ? Promise.resolve(r3.value.v).then(fulfill, reject) : settle(q4[0][2], r3);
3547
3547
  }
3548
3548
  function fulfill(value) {
3549
3549
  resume("next", value);
@@ -3551,7 +3551,7 @@ var require_index_cjs = __commonJS({
3551
3551
  function reject(value) {
3552
3552
  resume("throw", value);
3553
3553
  }
3554
- function settle2(f6, v2) {
3554
+ function settle(f6, v2) {
3555
3555
  if (f6(v2), q4.shift(), q4.length) resume(q4[0][0], q4[0][1]);
3556
3556
  }
3557
3557
  }
@@ -3577,11 +3577,11 @@ var require_index_cjs = __commonJS({
3577
3577
  function verb(n3) {
3578
3578
  i3[n3] = o3[n3] && function(v2) {
3579
3579
  return new Promise(function(resolve, reject) {
3580
- v2 = o3[n3](v2), settle2(resolve, reject, v2.done, v2.value);
3580
+ v2 = o3[n3](v2), settle(resolve, reject, v2.done, v2.value);
3581
3581
  });
3582
3582
  };
3583
3583
  }
3584
- function settle2(resolve, reject, d, v2) {
3584
+ function settle(resolve, reject, d, v2) {
3585
3585
  Promise.resolve(v2).then(function(v3) {
3586
3586
  resolve({ value: v3, done: d });
3587
3587
  }, reject);
@@ -19623,8 +19623,8 @@ var require_queue = __commonJS({
19623
19623
  var errorHandler = null;
19624
19624
  var self2 = {
19625
19625
  push,
19626
- drain: noop3,
19627
- saturated: noop3,
19626
+ drain: noop2,
19627
+ saturated: noop2,
19628
19628
  pause,
19629
19629
  paused: false,
19630
19630
  get concurrency() {
@@ -19647,7 +19647,7 @@ var require_queue = __commonJS({
19647
19647
  length,
19648
19648
  getQueue,
19649
19649
  unshift,
19650
- empty: noop3,
19650
+ empty: noop2,
19651
19651
  kill,
19652
19652
  killAndDrain,
19653
19653
  error: error48,
@@ -19699,7 +19699,7 @@ var require_queue = __commonJS({
19699
19699
  current.context = context;
19700
19700
  current.release = release;
19701
19701
  current.value = value;
19702
- current.callback = done || noop3;
19702
+ current.callback = done || noop2;
19703
19703
  current.errorHandler = errorHandler;
19704
19704
  if (_running >= _concurrency || self2.paused) {
19705
19705
  if (queueTail) {
@@ -19720,7 +19720,7 @@ var require_queue = __commonJS({
19720
19720
  current.context = context;
19721
19721
  current.release = release;
19722
19722
  current.value = value;
19723
- current.callback = done || noop3;
19723
+ current.callback = done || noop2;
19724
19724
  current.errorHandler = errorHandler;
19725
19725
  if (_running >= _concurrency || self2.paused) {
19726
19726
  if (queueHead) {
@@ -19762,13 +19762,13 @@ var require_queue = __commonJS({
19762
19762
  function kill() {
19763
19763
  queueHead = null;
19764
19764
  queueTail = null;
19765
- self2.drain = noop3;
19765
+ self2.drain = noop2;
19766
19766
  }
19767
19767
  function killAndDrain() {
19768
19768
  queueHead = null;
19769
19769
  queueTail = null;
19770
19770
  self2.drain();
19771
- self2.drain = noop3;
19771
+ self2.drain = noop2;
19772
19772
  }
19773
19773
  function abort() {
19774
19774
  var current = queueHead;
@@ -19781,7 +19781,7 @@ var require_queue = __commonJS({
19781
19781
  var val = current.value;
19782
19782
  var context2 = current.context;
19783
19783
  current.value = null;
19784
- current.callback = noop3;
19784
+ current.callback = noop2;
19785
19785
  current.errorHandler = null;
19786
19786
  if (errorHandler2) {
19787
19787
  errorHandler2(new Error("abort"), val);
@@ -19790,19 +19790,19 @@ var require_queue = __commonJS({
19790
19790
  current.release(current);
19791
19791
  current = next;
19792
19792
  }
19793
- self2.drain = noop3;
19793
+ self2.drain = noop2;
19794
19794
  }
19795
19795
  function error48(handler) {
19796
19796
  errorHandler = handler;
19797
19797
  }
19798
19798
  }
19799
- function noop3() {
19799
+ function noop2() {
19800
19800
  }
19801
19801
  function Task3() {
19802
19802
  this.value = null;
19803
- this.callback = noop3;
19803
+ this.callback = noop2;
19804
19804
  this.next = null;
19805
- this.release = noop3;
19805
+ this.release = noop2;
19806
19806
  this.context = null;
19807
19807
  this.errorHandler = null;
19808
19808
  var self2 = this;
@@ -19811,7 +19811,7 @@ var require_queue = __commonJS({
19811
19811
  var errorHandler = self2.errorHandler;
19812
19812
  var val = self2.value;
19813
19813
  self2.value = null;
19814
- self2.callback = noop3;
19814
+ self2.callback = noop2;
19815
19815
  if (self2.errorHandler) {
19816
19816
  errorHandler(err20, val);
19817
19817
  }
@@ -19847,7 +19847,7 @@ var require_queue = __commonJS({
19847
19847
  resolve(result);
19848
19848
  });
19849
19849
  });
19850
- p.catch(noop3);
19850
+ p.catch(noop2);
19851
19851
  return p;
19852
19852
  }
19853
19853
  function unshift(value) {
@@ -19860,7 +19860,7 @@ var require_queue = __commonJS({
19860
19860
  resolve(result);
19861
19861
  });
19862
19862
  });
19863
- p.catch(noop3);
19863
+ p.catch(noop2);
19864
19864
  return p;
19865
19865
  }
19866
19866
  function drained() {
@@ -52021,12 +52021,15 @@ var safeParseSimctlOutput = (0, import_neverthrow3.fromThrowable)(
52021
52021
  cause
52022
52022
  })
52023
52023
  );
52024
+ function findBootedDeviceUdid(devices) {
52025
+ const booted = devices.find((device) => device.state === "Booted");
52026
+ return booted?.udid;
52027
+ }
52024
52028
  function findBootedUdid(parsed) {
52025
52029
  for (const devices of Object.values(parsed.devices)) {
52026
- for (const device of devices) {
52027
- if (device.state === "Booted") {
52028
- return device.udid;
52029
- }
52030
+ const udid = findBootedDeviceUdid(devices);
52031
+ if (udid !== void 0) {
52032
+ return udid;
52030
52033
  }
52031
52034
  }
52032
52035
  return {
@@ -52062,24 +52065,6 @@ var DEFAULT_SCREEN_WIDTH = 375;
52062
52065
  var DEFAULT_SCREEN_HEIGHT = 812;
52063
52066
  var STABILITY_POLL_ATTEMPTS = 10;
52064
52067
  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
- }
52083
52068
  function isInViewport(frame, screen) {
52084
52069
  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;
52085
52070
  }
@@ -52098,14 +52083,16 @@ function hasEnabledDescendant(element) {
52098
52083
  const children = element.children ?? [];
52099
52084
  return children.some((child) => child.enabled !== false || hasEnabledDescendant(child));
52100
52085
  }
52086
+ function isRicher(current, best) {
52087
+ if (current.hasEnabled !== best.hasEnabled) {
52088
+ return current.hasEnabled;
52089
+ }
52090
+ return current.count >= best.count;
52091
+ }
52101
52092
  function selectRichest(scores) {
52102
52093
  let best = scores[0];
52103
52094
  for (const current of scores.slice(1)) {
52104
- if (current.hasEnabled !== best.hasEnabled) {
52105
- if (current.hasEnabled) {
52106
- best = current;
52107
- }
52108
- } else if (current.count >= best.count) {
52095
+ if (isRicher(current, best)) {
52109
52096
  best = current;
52110
52097
  }
52111
52098
  }
@@ -52179,18 +52166,6 @@ function formatElement(element, screen) {
52179
52166
  const clipping = resolveClippingTags(frame, screen);
52180
52167
  return `[${type2}] "${label}" at (${String(cx)}, ${String(cy)}) size ${String(Math.round(frame.width))}x${String(Math.round(frame.height))}${state}${clipping}`;
52181
52168
  }
52182
- function formatAccessibilityElements(elements) {
52183
- const app = elements.find((element) => element.type === "Application");
52184
- const screenWidth = app?.frame?.width ?? DEFAULT_SCREEN_WIDTH;
52185
- const screenHeight = app?.frame?.height ?? DEFAULT_SCREEN_HEIGHT;
52186
- const screen = { width: screenWidth, height: screenHeight };
52187
- const visible = collectElements(elements, screen);
52188
- const elementList = visible.length === 0 ? "No elements found." : visible.map((element) => formatElement(element, screen)).join("\n");
52189
- const appName = app?.AXLabel;
52190
- return appName ? `Running app: ${appName}
52191
-
52192
- ${elementList}` : elementList;
52193
- }
52194
52169
  function fetchSnapshot(udid) {
52195
52170
  return runCommand("idb", ["ui", "describe-all", "--udid", udid, "--json", "--nested"]);
52196
52171
  }
@@ -52220,7 +52195,37 @@ async function pollUntilStable(udid) {
52220
52195
  return (0, import_neverthrow.ok)(previousRaw);
52221
52196
  }
52222
52197
  function waitForStableSnapshot(udid) {
52223
- return new import_neverthrow.ResultAsync(pollUntilStable(udid));
52198
+ return import_neverthrow.ResultAsync.fromSafePromise(pollUntilStable(udid)).andThen((result) => result);
52199
+ }
52200
+ function structuralSignature(elements) {
52201
+ const lines = [];
52202
+ const walk = (nodes) => {
52203
+ for (const element of nodes) {
52204
+ const frame = element.frame ?? { x: 0, y: 0, width: 0, height: 0 };
52205
+ const type2 = element.type ?? "unknown";
52206
+ const childCount = (element.children ?? []).length;
52207
+ lines.push(
52208
+ `${type2}|${String(frame.x)},${String(frame.y)},${String(frame.width)},${String(frame.height)}|${String(childCount)}`
52209
+ );
52210
+ if (element.children) {
52211
+ walk(element.children);
52212
+ }
52213
+ }
52214
+ };
52215
+ walk(elements);
52216
+ return lines.join("\n");
52217
+ }
52218
+ function formatAccessibilityElements(elements) {
52219
+ const app = elements.find((element) => element.type === "Application");
52220
+ const screenWidth = app?.frame?.width ?? DEFAULT_SCREEN_WIDTH;
52221
+ const screenHeight = app?.frame?.height ?? DEFAULT_SCREEN_HEIGHT;
52222
+ const screen = { width: screenWidth, height: screenHeight };
52223
+ const visible = collectElements(elements, screen);
52224
+ const elementList = visible.length === 0 ? "No elements found." : visible.map((element) => formatElement(element, screen)).join("\n");
52225
+ const appName = app?.AXLabel;
52226
+ return appName ? `Running app: ${appName}
52227
+
52228
+ ${elementList}` : elementList;
52224
52229
  }
52225
52230
  function captureAccessibilityTree(udid = "booted") {
52226
52231
  return resolveUdid(udid).andThen((resolvedUdid) => waitForStableSnapshot(resolvedUdid)).andThen((raw) => parseIdbTree(raw));
@@ -52740,17 +52745,13 @@ function parseRuntime(runtimeKey) {
52740
52745
  }
52741
52746
  return match[1].replaceAll("-", ".");
52742
52747
  }
52748
+ function extractBootedFromDevices(devices, runtime) {
52749
+ return devices.filter((device) => device.state === "Booted" && device.isAvailable).map((device) => ({ udid: device.udid, name: device.name, runtime }));
52750
+ }
52743
52751
  function extractBootedSimulators(output) {
52744
- const results = [];
52745
- for (const [runtimeKey, devices] of Object.entries(output.devices)) {
52746
- const runtime = parseRuntime(runtimeKey);
52747
- for (const device of devices) {
52748
- if (device.state === "Booted" && device.isAvailable) {
52749
- results.push({ udid: device.udid, name: device.name, runtime });
52750
- }
52751
- }
52752
- }
52753
- return results;
52752
+ return Object.entries(output.devices).flatMap(
52753
+ ([runtimeKey, devices]) => extractBootedFromDevices(devices, parseRuntime(runtimeKey))
52754
+ );
52754
52755
  }
52755
52756
  function handleClose(code) {
52756
52757
  if (code === 0) {
@@ -53449,9 +53450,6 @@ function createInputManager(stream) {
53449
53450
  }
53450
53451
  };
53451
53452
  }
53452
- function plainDecorator(frame, deps) {
53453
- return frame.logAppend.flatMap((entry) => renderEntry(entry, deps)).join("");
53454
- }
53455
53453
  function renderEntry(entry, deps) {
53456
53454
  const context = { itemId: entry.itemId, deps };
53457
53455
  return entry.lines.flatMap((line) => renderLine(line, context));
@@ -53475,6 +53473,9 @@ function renderLine(line, context) {
53475
53473
  }
53476
53474
  }
53477
53475
  }
53476
+ function plainDecorator(frame, deps) {
53477
+ return frame.logAppend.flatMap((entry) => renderEntry(entry, deps)).join("");
53478
+ }
53478
53479
  var TRUECOLOR_LEVEL = 3;
53479
53480
  var ERROR_COLOR = "#c0504d";
53480
53481
  var TOOL_COLOR = "#7a9e82";
@@ -53575,7 +53576,7 @@ function emitTty(frame, state) {
53575
53576
  previousDashboardLines: state.previousDashboardLines,
53576
53577
  itemName: config3.itemName
53577
53578
  });
53578
- state.previousDashboardLines = newDashboardLines;
53579
+ state.setPreviousDashboardLines(newDashboardLines);
53579
53580
  if (output !== "") {
53580
53581
  config3.stream.write(output);
53581
53582
  }
@@ -53602,7 +53603,10 @@ function createWriter(config3) {
53602
53603
  const state = {
53603
53604
  config: config3,
53604
53605
  previousDashboardLines: 0,
53605
- cleaned: false
53606
+ cleaned: false,
53607
+ setPreviousDashboardLines(value) {
53608
+ this.previousDashboardLines = value;
53609
+ }
53606
53610
  };
53607
53611
  config3.stream.write(HIDE_CURSOR);
53608
53612
  return {
@@ -53622,30 +53626,6 @@ var OVERVIEW_HINT = "[1-9] zoom [e] expand [q] quit";
53622
53626
  var FOCUSED_HINT = "[esc] back [q] quit";
53623
53627
  var SOLO_HINT = "";
53624
53628
  var MIN_ITEMS_FOR_ZOOM = 2;
53625
- function buildDashboard(state) {
53626
- if (state.ui.role === "solo") {
53627
- return [];
53628
- }
53629
- if (typeof state.ui.zoomedIndex === "number") {
53630
- return buildZoomed(state, state.ui.zoomedIndex);
53631
- }
53632
- if (state.ui.isExpanded) {
53633
- return buildExpanded(state);
53634
- }
53635
- return buildOverview(state);
53636
- }
53637
- function buildHint(state) {
53638
- if (state.ui.role === "solo") {
53639
- return SOLO_HINT;
53640
- }
53641
- if (typeof state.ui.zoomedIndex === "number" || state.ui.isExpanded) {
53642
- return FOCUSED_HINT;
53643
- }
53644
- if (state.itemOrder.length < MIN_ITEMS_FOR_ZOOM) {
53645
- return "[q] quit";
53646
- }
53647
- return OVERVIEW_HINT;
53648
- }
53649
53629
  function buildOverview(state) {
53650
53630
  return state.itemOrder.flatMap((id, index) => {
53651
53631
  const item = state.items.get(id);
@@ -53694,6 +53674,33 @@ function prefixTextLine(line, name) {
53694
53674
  }
53695
53675
  return [{ ...line, text: `[${name}] ${line.text}` }];
53696
53676
  }
53677
+ function buildDashboard(state) {
53678
+ if (state.ui.role === "solo") {
53679
+ return [];
53680
+ }
53681
+ if (typeof state.ui.zoomedIndex === "number") {
53682
+ return buildZoomed(state, state.ui.zoomedIndex);
53683
+ }
53684
+ if (state.ui.isExpanded) {
53685
+ return buildExpanded(state);
53686
+ }
53687
+ return buildOverview(state);
53688
+ }
53689
+ function buildHint(state) {
53690
+ if (state.ui.role === "solo") {
53691
+ return SOLO_HINT;
53692
+ }
53693
+ if (typeof state.ui.zoomedIndex === "number" || state.ui.isExpanded) {
53694
+ return FOCUSED_HINT;
53695
+ }
53696
+ if (state.itemOrder.length < MIN_ITEMS_FOR_ZOOM) {
53697
+ return "[q] quit";
53698
+ }
53699
+ return OVERVIEW_HINT;
53700
+ }
53701
+ function dashboardEqual(left, right) {
53702
+ return JSON.stringify(left) === JSON.stringify(right);
53703
+ }
53697
53704
  function render(state, previous) {
53698
53705
  const dashboard = buildDashboard(state);
53699
53706
  const logAppend = state.log.length > previous.log.length ? state.log.slice(previous.log.length) : [];
@@ -53705,9 +53712,6 @@ function render(state, previous) {
53705
53712
  dashboardChanged: !dashboardEqual(previousDashboard, dashboard)
53706
53713
  };
53707
53714
  }
53708
- function dashboardEqual(left, right) {
53709
- return JSON.stringify(left) === JSON.stringify(right);
53710
- }
53711
53715
  var MS_PER_SECOND3 = 1e3;
53712
53716
  function formatDuration(ms) {
53713
53717
  return `${String(Math.round(ms / MS_PER_SECOND3))}s`;
@@ -53789,6 +53793,22 @@ var ALL_VERBOSE_CATEGORIES = /* @__PURE__ */ new Set([
53789
53793
  "screen",
53790
53794
  "memory"
53791
53795
  ]);
53796
+ function parseForceColor(value) {
53797
+ const forced = Number(value);
53798
+ if (forced === 0 || forced === 1 || forced === 2 || forced === COLOR_LEVEL_TRUECOLOR) {
53799
+ return forced;
53800
+ }
53801
+ return 1;
53802
+ }
53803
+ function isTruecolorTerm(colorterm) {
53804
+ return colorterm === "truecolor" || colorterm === "24bit";
53805
+ }
53806
+ function detectStreamColorLevel(stream) {
53807
+ if (stream.hasColors?.() === true) {
53808
+ return 1;
53809
+ }
53810
+ return 0;
53811
+ }
53792
53812
  function isVerboseEnabled(config3, category) {
53793
53813
  return config3?.has(category) ?? false;
53794
53814
  }
@@ -53806,19 +53826,12 @@ function resolveColorLevel(stream) {
53806
53826
  return 0;
53807
53827
  }
53808
53828
  if (process.env.FORCE_COLOR !== void 0) {
53809
- const forced = Number(process.env.FORCE_COLOR);
53810
- if (forced === 0 || forced === 1 || forced === 2 || forced === COLOR_LEVEL_TRUECOLOR) {
53811
- return forced;
53812
- }
53813
- return 1;
53829
+ return parseForceColor(process.env.FORCE_COLOR);
53814
53830
  }
53815
- if (process.env.COLORTERM === "truecolor" || process.env.COLORTERM === "24bit") {
53831
+ if (isTruecolorTerm(process.env.COLORTERM)) {
53816
53832
  return COLOR_LEVEL_TRUECOLOR;
53817
53833
  }
53818
- if (!stream.hasColors?.()) {
53819
- return 0;
53820
- }
53821
- return 1;
53834
+ return detectStreamColorLevel(stream);
53822
53835
  }
53823
53836
  var BYTES_PER_BASE64_CHAR = 0.75;
53824
53837
  var BYTES_PER_KB = 1024;
@@ -54504,17 +54517,6 @@ function startSpinner(config3, onEvent) {
54504
54517
  onEvent({ type: "TICK", at: Date.now() });
54505
54518
  }, SPINNER_INTERVAL_MS);
54506
54519
  }
54507
- function cleanupRuntime(runtime) {
54508
- if (runtime.cleaned) {
54509
- return;
54510
- }
54511
- runtime.cleaned = true;
54512
- if (runtime.spinnerHandle !== void 0) {
54513
- clearInterval(runtime.spinnerHandle);
54514
- }
54515
- runtime.writer?.cleanup();
54516
- runtime.inputManager?.cleanup();
54517
- }
54518
54520
  function createRuntime(role) {
54519
54521
  return {
54520
54522
  state: initialState(role),
@@ -54523,7 +54525,18 @@ function createRuntime(role) {
54523
54525
  writer: void 0,
54524
54526
  inputManager: void 0,
54525
54527
  spinnerHandle: void 0,
54526
- cleaned: false
54528
+ cleaned: false,
54529
+ cleanup() {
54530
+ if (this.cleaned) {
54531
+ return;
54532
+ }
54533
+ this.cleaned = true;
54534
+ if (this.spinnerHandle !== void 0) {
54535
+ clearInterval(this.spinnerHandle);
54536
+ }
54537
+ this.writer?.cleanup();
54538
+ this.inputManager?.cleanup();
54539
+ }
54527
54540
  };
54528
54541
  }
54529
54542
  function createDisplay(config3) {
@@ -54540,7 +54553,7 @@ function createDisplay(config3) {
54540
54553
  return {
54541
54554
  onEvent,
54542
54555
  cleanup: () => {
54543
- cleanupRuntime(runtime);
54556
+ runtime.cleanup();
54544
54557
  }
54545
54558
  };
54546
54559
  }
@@ -54943,11 +54956,11 @@ function __asyncGenerator(thisArg, _arguments, generator) {
54943
54956
  try {
54944
54957
  step(g3[n3](v2));
54945
54958
  } catch (e3) {
54946
- settle2(q4[0][3], e3);
54959
+ settle(q4[0][3], e3);
54947
54960
  }
54948
54961
  }
54949
54962
  function step(r3) {
54950
- r3.value instanceof __await ? Promise.resolve(r3.value.v).then(fulfill, reject) : settle2(q4[0][2], r3);
54963
+ r3.value instanceof __await ? Promise.resolve(r3.value.v).then(fulfill, reject) : settle(q4[0][2], r3);
54951
54964
  }
54952
54965
  function fulfill(value) {
54953
54966
  resume("next", value);
@@ -54955,7 +54968,7 @@ function __asyncGenerator(thisArg, _arguments, generator) {
54955
54968
  function reject(value) {
54956
54969
  resume("throw", value);
54957
54970
  }
54958
- function settle2(f6, v2) {
54971
+ function settle(f6, v2) {
54959
54972
  if (f6(v2), q4.shift(), q4.length) resume(q4[0][0], q4[0][1]);
54960
54973
  }
54961
54974
  }
@@ -56006,30 +56019,6 @@ async function uploadVideoFile(fileManager, videoPath) {
56006
56019
  });
56007
56020
  return uploadResponse.file.uri;
56008
56021
  }
56009
- function uploadVideo(videoPath, apiKey) {
56010
- const fileManager = new GoogleAIFileManager(apiKey);
56011
- return (0, import_neverthrow11.fromAsyncThrowable)(
56012
- uploadVideoFile,
56013
- (cause) => ({ type: "UPLOAD_FAILED", cause })
56014
- )(fileManager, videoPath);
56015
- }
56016
- function pollUntilActive(fileUri, options) {
56017
- const fileManager = new GoogleAIFileManager(options.apiKey);
56018
- const fileName = extractFileName(fileUri);
56019
- const checkState = async () => checkFileState(fileManager, fileName);
56020
- return (0, import_neverthrow11.fromAsyncThrowable)(
56021
- runPollLoop,
56022
- (cause) => ({ type: "UPLOAD_FAILED", cause })
56023
- )(checkState, options.onTick).andThen((outcome) => {
56024
- if (outcome === "active") {
56025
- return (0, import_neverthrow11.ok)(fileUri);
56026
- }
56027
- if (outcome === "failed") {
56028
- return (0, import_neverthrow11.err)({ type: "PROCESSING_FAILED", fileUri });
56029
- }
56030
- return (0, import_neverthrow11.err)({ type: "POLL_TIMEOUT", fileUri });
56031
- });
56032
- }
56033
56022
  async function generateAnalysisText({
56034
56023
  genModel,
56035
56024
  videoUri,
@@ -56063,6 +56052,30 @@ function buildGenerativeModel(apiKey, model) {
56063
56052
  }
56064
56053
  });
56065
56054
  }
56055
+ function uploadVideo(videoPath, apiKey) {
56056
+ const fileManager = new GoogleAIFileManager(apiKey);
56057
+ return (0, import_neverthrow11.fromAsyncThrowable)(
56058
+ uploadVideoFile,
56059
+ (cause) => ({ type: "UPLOAD_FAILED", cause })
56060
+ )(fileManager, videoPath);
56061
+ }
56062
+ function pollUntilActive(fileUri, options) {
56063
+ const fileManager = new GoogleAIFileManager(options.apiKey);
56064
+ const fileName = extractFileName(fileUri);
56065
+ const checkState = async () => checkFileState(fileManager, fileName);
56066
+ return (0, import_neverthrow11.fromAsyncThrowable)(
56067
+ runPollLoop,
56068
+ (cause) => ({ type: "UPLOAD_FAILED", cause })
56069
+ )(checkState, options.onTick).andThen((outcome) => {
56070
+ if (outcome === "active") {
56071
+ return (0, import_neverthrow11.ok)(fileUri);
56072
+ }
56073
+ if (outcome === "failed") {
56074
+ return (0, import_neverthrow11.err)({ type: "PROCESSING_FAILED", fileUri });
56075
+ }
56076
+ return (0, import_neverthrow11.err)({ type: "POLL_TIMEOUT", fileUri });
56077
+ });
56078
+ }
56066
56079
  function analyseArtifacts(options) {
56067
56080
  const { videoUri, prompt, apiKey, model } = options;
56068
56081
  const genModel = buildGenerativeModel(apiKey, model);
@@ -62231,7 +62244,6 @@ var import_neverthrow21 = __toESM(require_index_cjs(), 1);
62231
62244
  var import_promises12 = require("node:fs/promises");
62232
62245
  var import_node_path6 = __toESM(require("node:path"), 1);
62233
62246
  var import_neverthrow22 = __toESM(require_index_cjs(), 1);
62234
- var VIEW_UI_TOOL_NAME = "mcp__mobile-ios__view_ui";
62235
62247
  var VIEW_UI_DESCRIPTION = `Capture current screen state: accessibility tree (element labels, positions, attributes) and screenshot in one call. Use when you need to tap an element, assert element presence or labels, check attributes, or track screen identity via <screen_id>.
62236
62248
 
62237
62249
  The result begins with a <screen_id> tag containing the current screen identifier. Use this to detect screen changes and track navigation history.
@@ -62239,14 +62251,9 @@ The result begins with a <screen_id> tag containing the current screen identifie
62239
62251
  Do not call \`screenshot\` immediately before or after this tool for the same state \u2014 this tool already includes the screenshot.
62240
62252
 
62241
62253
  IMPORTANT: Snapshot coordinates and screenshot pixels are in the same logical point space. Do not apply any scaling factor (no 2x retina adjustment).`;
62242
- function deriveScreenLabel(tree, stepIndex) {
62243
- const match = /\[\w+\]\s+"([^"]+)"/.exec(tree);
62244
- if (!match?.[1]) {
62245
- return `step-${String(stepIndex)}`;
62246
- }
62247
- return match[1].replaceAll(/([A-Z])/g, "-$1").toLowerCase().replaceAll(/[^a-z\d]+/g, "-").replaceAll(/^-|-$/g, "");
62248
- }
62254
+ var VIEW_UI_TOOL_NAME = "mcp__mobile-ios__view_ui";
62249
62255
  async function persistScreenshot(params) {
62256
+ const targetSnapshot = params.snapshot;
62250
62257
  const screenshotPath = import_node_path4.default.join(params.screenshotsDirectory, `${params.screenLabel}.png`);
62251
62258
  const safeWriteFile = (0, import_neverthrow17.fromAsyncThrowable)(
62252
62259
  import_promises10.writeFile,
@@ -62258,7 +62265,7 @@ async function persistScreenshot(params) {
62258
62265
  () => false
62259
62266
  );
62260
62267
  if (succeeded) {
62261
- params.snapshot.screenshotPath = screenshotPath;
62268
+ targetSnapshot.screenshotPath = screenshotPath;
62262
62269
  }
62263
62270
  }
62264
62271
  function buildTextContent(structuralHash, formatted) {
@@ -62337,13 +62344,14 @@ async function enrichContentWithScreenshot(params) {
62337
62344
  if (data) {
62338
62345
  await handleScreenshotData(data, params);
62339
62346
  }
62340
- params.lastEmittedHash.value = maybeEmitVisualStep({
62347
+ const hashReference = params.lastEmittedHash;
62348
+ hashReference.value = maybeEmitVisualStep({
62341
62349
  snapshot: params.snapshot,
62342
62350
  stepIndex: params.stepIndex,
62343
62351
  formatted: params.formatted,
62344
62352
  structuralHash: params.structuralHash,
62345
62353
  screenLabel: params.screenLabel,
62346
- lastEmittedHash: params.lastEmittedHash.value,
62354
+ lastEmittedHash: hashReference.value,
62347
62355
  onEvent: params.context.onEvent
62348
62356
  });
62349
62357
  }
@@ -62392,11 +62400,19 @@ async function runViewUiCapture(context, state) {
62392
62400
  function createViewUiHandler(context) {
62393
62401
  const lastEmittedHash = { value: void 0 };
62394
62402
  const labelCounters = /* @__PURE__ */ new Map();
62403
+ const stepCounterReference = context.stepCounter;
62395
62404
  return async () => {
62396
- const stepIndex = context.stepCounter.value++;
62405
+ const stepIndex = stepCounterReference.value++;
62397
62406
  return runViewUiCapture(context, { stepIndex, lastEmittedHash, labelCounters });
62398
62407
  };
62399
62408
  }
62409
+ function deriveScreenLabel(tree, stepIndex) {
62410
+ const match = /\[\w+\]\s+"([^"]+)"/.exec(tree);
62411
+ if (!match?.[1]) {
62412
+ return `step-${String(stepIndex)}`;
62413
+ }
62414
+ return match[1].replaceAll(/([A-Z])/g, "-$1").toLowerCase().replaceAll(/[^a-z\d]+/g, "-").replaceAll(/^-|-$/g, "");
62415
+ }
62400
62416
  function createViewUiTool(context) {
62401
62417
  return _x("view_ui", VIEW_UI_DESCRIPTION, {}, createViewUiHandler(context));
62402
62418
  }
@@ -62423,19 +62439,6 @@ function captureToolUse(blk, state) {
62423
62439
  state.closeQueue();
62424
62440
  }
62425
62441
  }
62426
- function captureFromAssistant(content, state) {
62427
- for (const block of content) {
62428
- if (typeof block !== "object" || block === null) {
62429
- continue;
62430
- }
62431
- const blk = block;
62432
- if ((blk.type === "thinking" || blk.type === "text") && typeof blk[blk.type] === "string") {
62433
- captureThinkingOrText(blk, state);
62434
- } else if (blk.type === "tool_use") {
62435
- captureToolUse(blk, state);
62436
- }
62437
- }
62438
- }
62439
62442
  function extractResultText(block) {
62440
62443
  const typed = block;
62441
62444
  if (typeof typed.content === "string") {
@@ -62448,27 +62451,49 @@ function extractResultText(block) {
62448
62451
  (item) => typeof item === "object" && item !== null && item.type === "text"
62449
62452
  ).map((item) => item.text).join("\n");
62450
62453
  }
62451
- function captureFromUser(content, state) {
62454
+ function emitToolResultEvent(emission) {
62455
+ const { block, toolName, blk, state } = emission;
62456
+ if (toolName === VIEW_UI_TOOL_NAME) {
62457
+ return;
62458
+ }
62459
+ const resultText = extractResultText(block);
62460
+ if (blk.is_error === true) {
62461
+ state.onEvent?.({ type: "TOOL_ERROR", agent: "explorer", toolName, error: resultText });
62462
+ return;
62463
+ }
62464
+ state.onEvent?.({ type: "TOOL_RESULT", agent: "explorer", toolName, result: resultText });
62465
+ }
62466
+ function captureToolResult(block, state) {
62467
+ const blk = block;
62468
+ if (blk.type !== "tool_result" || typeof blk.tool_use_id !== "string") {
62469
+ return;
62470
+ }
62471
+ const toolName = state.pendingToolCallNames.get(blk.tool_use_id);
62472
+ if (toolName === void 0) {
62473
+ return;
62474
+ }
62475
+ emitToolResultEvent({ block, toolName, blk, state });
62476
+ state.pendingToolCallNames.delete(blk.tool_use_id);
62477
+ }
62478
+ function captureFromAssistant(content, state) {
62452
62479
  for (const block of content) {
62453
62480
  if (typeof block !== "object" || block === null) {
62454
62481
  continue;
62455
62482
  }
62456
62483
  const blk = block;
62457
- if (blk.type !== "tool_result" || typeof blk.tool_use_id !== "string") {
62458
- continue;
62484
+ if ((blk.type === "thinking" || blk.type === "text") && typeof blk[blk.type] === "string") {
62485
+ captureThinkingOrText(blk, state);
62486
+ } else if (blk.type === "tool_use") {
62487
+ captureToolUse(blk, state);
62459
62488
  }
62460
- const toolName = state.pendingToolCallNames.get(blk.tool_use_id);
62461
- if (toolName !== void 0) {
62462
- if (toolName !== VIEW_UI_TOOL_NAME) {
62463
- const resultText = extractResultText(block);
62464
- if (blk.is_error === true) {
62465
- state.onEvent?.({ type: "TOOL_ERROR", agent: "explorer", toolName, error: resultText });
62466
- } else {
62467
- state.onEvent?.({ type: "TOOL_RESULT", agent: "explorer", toolName, result: resultText });
62468
- }
62469
- }
62470
- state.pendingToolCallNames.delete(blk.tool_use_id);
62489
+ }
62490
+ }
62491
+ function captureFromUser(content, state) {
62492
+ for (const block of content) {
62493
+ if (typeof block !== "object" || block === null) {
62494
+ continue;
62471
62495
  }
62496
+ captureToolResult(block, state);
62472
62497
  }
62473
62498
  }
62474
62499
  function processMessage(message, state) {
@@ -62655,8 +62680,9 @@ function buildInterruptErrorEvent(agent, error48) {
62655
62680
  return { type: "ERROR", agent, message, stack };
62656
62681
  }
62657
62682
  function startHardTimer(gracePeriodMs, context) {
62683
+ const timedOutReference = context.state.timedOut;
62658
62684
  return setTimeout(() => {
62659
- context.state.timedOut.value = true;
62685
+ timedOutReference.value = true;
62660
62686
  context.queryRunner.interrupt().catch((error48) => {
62661
62687
  context.state.onEvent?.(buildInterruptErrorEvent(context.agent, error48));
62662
62688
  });
@@ -62668,6 +62694,7 @@ function startSoftTimer({
62668
62694
  hardTimerRef,
62669
62695
  context
62670
62696
  }) {
62697
+ const targetReference = hardTimerRef;
62671
62698
  return setTimeout(() => {
62672
62699
  context.state.onEvent?.({
62673
62700
  type: "TIMEOUT_GRACE_ENTERED",
@@ -62675,7 +62702,7 @@ function startSoftTimer({
62675
62702
  gracePeriodMs
62676
62703
  });
62677
62704
  context.state.sendMessage(SOFT_DEADLINE_MESSAGE);
62678
- hardTimerRef.value = startHardTimer(gracePeriodMs, context);
62705
+ targetReference.value = startHardTimer(gracePeriodMs, context);
62679
62706
  }, timeoutMs);
62680
62707
  }
62681
62708
  function startActiveTimeout({
@@ -62727,8 +62754,9 @@ async function drainInterruptThenAbort(context) {
62727
62754
  linkedController?.abort(reason);
62728
62755
  }
62729
62756
  function registerSignalAbortListeners(config3, { queryRunner, state, linkedController }) {
62757
+ const abortedReference = state.aborted;
62730
62758
  const onAbort = () => {
62731
- state.aborted.value = true;
62759
+ abortedReference.value = true;
62732
62760
  void drainInterruptThenAbort({
62733
62761
  queryRunner,
62734
62762
  state,
@@ -62756,16 +62784,16 @@ function startQueryTimers(config3, context) {
62756
62784
  inputQueue.close();
62757
62785
  };
62758
62786
  }
62759
- function awaitMessagesAndResolve({ queryRunner, state }, { cleanup: cleanup3, getOutput }) {
62787
+ function awaitMessagesAndResolve({ queryRunner, state }, { cleanup: cleanup2, getOutput }) {
62760
62788
  const messagesPromise = processMessages(queryRunner, state);
62761
62789
  return import_neverthrow15.ResultAsync.fromPromise(messagesPromise, String).andThen((innerResult) => {
62762
- cleanup3();
62790
+ cleanup2();
62763
62791
  if (innerResult.isErr()) {
62764
62792
  return (0, import_neverthrow15.err)(innerResult.error);
62765
62793
  }
62766
62794
  return (0, import_neverthrow15.ok)(getOutput());
62767
62795
  }).orElse((sdkError) => {
62768
- cleanup3();
62796
+ cleanup2();
62769
62797
  if (!state.timedOut.value && !state.aborted.value) {
62770
62798
  return (0, import_neverthrow15.err)(sdkError);
62771
62799
  }
@@ -62788,14 +62816,14 @@ function executeQuery({
62788
62816
  return (0, import_neverthrow15.errAsync)(queryRunnerResult.error);
62789
62817
  }
62790
62818
  const queryRunner = queryRunnerResult.value;
62791
- const cleanup3 = startQueryTimers(config3, { state, queryRunner, inputQueue, linkedController });
62819
+ const cleanup2 = startQueryTimers(config3, { state, queryRunner, inputQueue, linkedController });
62792
62820
  const getOutput = () => ({
62793
62821
  findings: (outputTools.getOutput()?.findings ?? []).map(
62794
62822
  (raw) => stampExplorerAgent(raw)
62795
62823
  ),
62796
62824
  snapshots: state.snapshots
62797
62825
  });
62798
- return awaitMessagesAndResolve({ queryRunner, state }, { cleanup: cleanup3, getOutput });
62826
+ return awaitMessagesAndResolve({ queryRunner, state }, { cleanup: cleanup2, getOutput });
62799
62827
  }
62800
62828
  function runQuery(prompt, config3) {
62801
62829
  const outputTools = createOutputTool({ findings: external_exports.array(EXPLORER_FINDING_SCHEMA) });
@@ -63124,15 +63152,6 @@ var FREESTYLE_TEMPLATE = (options) => {
63124
63152
  ${DEV_ENVIRONMENT_SECTION}` : "";
63125
63153
  return buildFreestyleBody({ contextBlock, environmentSection });
63126
63154
  };
63127
- function generateExplorerPrompt({
63128
- mode,
63129
- specs,
63130
- appContext,
63131
- initialState: initialState2,
63132
- buildEnv
63133
- }) {
63134
- return mode === "spec" ? buildSpecModePrompt(specs, { appContext, initialState: initialState2, buildEnv }) : FREESTYLE_TEMPLATE({ appContext, initialState: initialState2, buildEnv });
63135
- }
63136
63155
  function renderStep(step, index) {
63137
63156
  const stepNumber = String(index + 1);
63138
63157
  const base = `${stepNumber}. ${step.action}`;
@@ -63160,6 +63179,15 @@ function buildSpecModePrompt(specs, options) {
63160
63179
  const specContent = specs.map((spec) => renderSpec(spec)).join("\n\n---\n\n");
63161
63180
  return SPEC_MODE_TEMPLATE(specContent, options);
63162
63181
  }
63182
+ function generateExplorerPrompt({
63183
+ mode,
63184
+ specs,
63185
+ appContext,
63186
+ initialState: initialState2,
63187
+ buildEnv
63188
+ }) {
63189
+ return mode === "spec" ? buildSpecModePrompt(specs, { appContext, initialState: initialState2, buildEnv }) : FREESTYLE_TEMPLATE({ appContext, initialState: initialState2, buildEnv });
63190
+ }
63163
63191
  var FRONTMATTER_FENCE = "---";
63164
63192
  var INLINE_ASSERTION_DELIMITER = " \u2192 ";
63165
63193
  var NUMBERED_STEP_PREFIX = /^\d+\.\s+/;
@@ -63283,10 +63311,6 @@ function parseTestSpec(name, content) {
63283
63311
  });
63284
63312
  });
63285
63313
  }
63286
- function resolveSpecs(config3, repoRoot = process.cwd()) {
63287
- const source = config3.specFiles && config3.specFiles.length > 0 ? loadFromFiles(config3.specFiles) : loadFromDirectory(import_node_path6.default.join(repoRoot, "openspec", "specs"));
63288
- return source.map((specs) => filterByNames(specs, config3.specNames));
63289
- }
63290
63314
  function loadFromFiles(paths) {
63291
63315
  const entries = paths.map((filePath) => ({
63292
63316
  path: filePath,
@@ -63357,6 +63381,10 @@ function filterByNames(specs, specNames) {
63357
63381
  }
63358
63382
  return specs.filter((spec) => specNames.includes(spec.name));
63359
63383
  }
63384
+ function resolveSpecs(config3, repoRoot = process.cwd()) {
63385
+ const source = config3.specFiles && config3.specFiles.length > 0 ? loadFromFiles(config3.specFiles) : loadFromDirectory(import_node_path6.default.join(repoRoot, "openspec", "specs"));
63386
+ return source.map((specs) => filterByNames(specs, config3.specNames));
63387
+ }
63360
63388
  var ISO_DATE_LENGTH = 10;
63361
63389
  function buildPrompt(safeConfig, specs) {
63362
63390
  return generateExplorerPrompt({
@@ -66105,6 +66133,19 @@ var jsYaml = {
66105
66133
  // ../../agents/inspector/dist/index.js
66106
66134
  var import_neverthrow29 = __toESM(require_index_cjs(), 1);
66107
66135
  var MS_PER_DAY = 864e5;
66136
+ function dequeue(context, current) {
66137
+ if (current.remainingQueue.length === 0 || context.activeCount + current.events.length >= context.maxConcurrency) {
66138
+ return current;
66139
+ }
66140
+ if (context.signal?.aborted === true) {
66141
+ return { events: current.events, remainingQueue: [] };
66142
+ }
66143
+ const [event, ...rest] = current.remainingQueue;
66144
+ if (!event) {
66145
+ return { events: current.events, remainingQueue: rest };
66146
+ }
66147
+ return dequeue(context, { events: [...current.events, event], remainingQueue: rest });
66148
+ }
66108
66149
  function checkStaleness({ lastUpdated, thresholdDays, now }) {
66109
66150
  if (!lastUpdated) {
66110
66151
  return { isStale: false, ageDays: 0 };
@@ -66119,19 +66160,6 @@ function tryResolve(state) {
66119
66160
  }
66120
66161
  return { shouldResolve: false, result };
66121
66162
  }
66122
- function dequeue(context, current) {
66123
- if (current.remainingQueue.length === 0 || context.activeCount + current.events.length >= context.maxConcurrency) {
66124
- return current;
66125
- }
66126
- if (context.signal?.aborted === true) {
66127
- return { events: current.events, remainingQueue: [] };
66128
- }
66129
- const [event, ...rest] = current.remainingQueue;
66130
- if (!event) {
66131
- return { events: current.events, remainingQueue: rest };
66132
- }
66133
- return dequeue(context, { events: [...current.events, event], remainingQueue: rest });
66134
- }
66135
66163
  function scheduleNext(state, maxConcurrency) {
66136
66164
  if (state.signal?.aborted === true) {
66137
66165
  return { events: [], remainingQueue: [] };
@@ -66144,16 +66172,6 @@ function scheduleNext(state, maxConcurrency) {
66144
66172
  var CONFIDENCE_HIGH = 0.9;
66145
66173
  var CONFIDENCE_MEDIUM = 0.6;
66146
66174
  var CONFIDENCE_LOW = 0.3;
66147
- function isValidSeverity(value) {
66148
- return value === "low" || value === "medium" || value === "high";
66149
- }
66150
- function isRawFinding(value) {
66151
- if (typeof value !== "object" || value === null || Array.isArray(value)) {
66152
- return false;
66153
- }
66154
- const object2 = value;
66155
- return typeof object2.type === "string" && typeof object2.element === "string" && typeof object2.description === "string" && typeof object2.severity === "string" && isValidSeverity(object2.severity);
66156
- }
66157
66175
  function toTriggerType(type2) {
66158
66176
  return type2 === "localization-issue" ? "localization-issue" : "visual-regression";
66159
66177
  }
@@ -66166,6 +66184,19 @@ function toConfidence(severity) {
66166
66184
  }
66167
66185
  return CONFIDENCE_LOW;
66168
66186
  }
66187
+ function parseJson(raw) {
66188
+ return JSON.parse(raw);
66189
+ }
66190
+ function isValidSeverity(value) {
66191
+ return value === "low" || value === "medium" || value === "high";
66192
+ }
66193
+ function isRawFinding(value) {
66194
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
66195
+ return false;
66196
+ }
66197
+ const object2 = value;
66198
+ return typeof object2.type === "string" && typeof object2.element === "string" && typeof object2.description === "string" && typeof object2.severity === "string" && isValidSeverity(object2.severity);
66199
+ }
66169
66200
  function mapRawFinding(item) {
66170
66201
  return {
66171
66202
  triggerType: toTriggerType(item.type),
@@ -66177,9 +66208,6 @@ function mapRawFinding(item) {
66177
66208
  agent: "inspector"
66178
66209
  };
66179
66210
  }
66180
- function parseJson(raw) {
66181
- return JSON.parse(raw);
66182
- }
66183
66211
  var safeJsonParse3 = (0, import_neverthrow26.fromThrowable)(parseJson);
66184
66212
  function parseClaudeResponse(raw) {
66185
66213
  const parseResult = safeJsonParse3(raw);
@@ -66402,25 +66430,6 @@ var SEVERITY_CONFIDENCE = {
66402
66430
  low: CONFIDENCE_LOW2
66403
66431
  };
66404
66432
  var safeJsonParse22 = (0, import_neverthrow28.fromThrowable)(JSON.parse);
66405
- function buildFindCandidatesMessages(screenshotBase64, artboardNames) {
66406
- return [
66407
- {
66408
- role: "user",
66409
- content: [
66410
- {
66411
- type: "image",
66412
- source: { type: "base64", media_type: "image/png", data: screenshotBase64 }
66413
- },
66414
- {
66415
- type: "text",
66416
- text: `Which of these design artboards are most visually similar to this screenshot (same app, same design system)? Reply with a JSON array of up to ${String(MAX_CANDIDATES)} artboard names from the list below, or [] if none are sufficiently similar. Only include names exactly as listed.
66417
-
66418
- Artboards: ${artboardNames.join(", ")}`
66419
- }
66420
- ]
66421
- }
66422
- ];
66423
- }
66424
66433
  function resolveNamesFromParsed(parsed, artboardNames) {
66425
66434
  return parsed.filter((name) => typeof name === "string").map((name) => {
66426
66435
  const lower = name.toLowerCase();
@@ -66446,19 +66455,6 @@ async function fetchCandidateNames({
66446
66455
  }
66447
66456
  return resolveNamesFromParsed(parsed.value, artboardNames);
66448
66457
  }
66449
- function findCandidates(screenshot, artboardNames) {
66450
- if (artboardNames.length === 0) {
66451
- return (0, import_neverthrow28.okAsync)([]);
66452
- }
66453
- const anthropic = new Anthropic();
66454
- return downscale(screenshot).andThen((scaled) => {
66455
- const screenshotBase64 = scaled.toString("base64");
66456
- return (0, import_neverthrow28.fromAsyncThrowable)(
66457
- fetchCandidateNames,
66458
- (cause) => ({ type: "CLAUDE_API_FAILED", cause })
66459
- )({ anthropic, screenshotBase64, artboardNames });
66460
- });
66461
- }
66462
66458
  function buildArtboardTextBlock(index, total) {
66463
66459
  const base = {
66464
66460
  type: "text",
@@ -66478,24 +66474,6 @@ function buildArtboardContentBlocks(artboardBase64s) {
66478
66474
  buildArtboardTextBlock(index, artboardBase64s.length)
66479
66475
  ]);
66480
66476
  }
66481
- function buildDesignContextMessages(screenshotBase64, artboardBase64s) {
66482
- return [
66483
- {
66484
- role: "user",
66485
- content: [
66486
- ...buildArtboardContentBlocks(artboardBase64s),
66487
- {
66488
- type: "image",
66489
- source: { type: "base64", media_type: "image/png", data: screenshotBase64 }
66490
- },
66491
- {
66492
- type: "text",
66493
- text: "This is the actual screenshot. List all design system violations as JSON."
66494
- }
66495
- ]
66496
- }
66497
- ];
66498
- }
66499
66477
  function parseConservativeResponse(raw) {
66500
66478
  const parseResult = safeJsonParse22(raw);
66501
66479
  if (parseResult.isErr()) {
@@ -66547,6 +66525,56 @@ function toDesignContextFindings(text) {
66547
66525
  async function downscaleAll(buffers) {
66548
66526
  return Promise.all(buffers.map(async (buf) => downscaleBuffer(buf)));
66549
66527
  }
66528
+ function buildFindCandidatesMessages(screenshotBase64, artboardNames) {
66529
+ return [
66530
+ {
66531
+ role: "user",
66532
+ content: [
66533
+ {
66534
+ type: "image",
66535
+ source: { type: "base64", media_type: "image/png", data: screenshotBase64 }
66536
+ },
66537
+ {
66538
+ type: "text",
66539
+ text: `Which of these design artboards are most visually similar to this screenshot (same app, same design system)? Reply with a JSON array of up to ${String(MAX_CANDIDATES)} artboard names from the list below, or [] if none are sufficiently similar. Only include names exactly as listed.
66540
+
66541
+ Artboards: ${artboardNames.join(", ")}`
66542
+ }
66543
+ ]
66544
+ }
66545
+ ];
66546
+ }
66547
+ function findCandidates(screenshot, artboardNames) {
66548
+ if (artboardNames.length === 0) {
66549
+ return (0, import_neverthrow28.okAsync)([]);
66550
+ }
66551
+ const anthropic = new Anthropic();
66552
+ return downscale(screenshot).andThen((scaled) => {
66553
+ const screenshotBase64 = scaled.toString("base64");
66554
+ return (0, import_neverthrow28.fromAsyncThrowable)(
66555
+ fetchCandidateNames,
66556
+ (cause) => ({ type: "CLAUDE_API_FAILED", cause })
66557
+ )({ anthropic, screenshotBase64, artboardNames });
66558
+ });
66559
+ }
66560
+ function buildDesignContextMessages(screenshotBase64, artboardBase64s) {
66561
+ return [
66562
+ {
66563
+ role: "user",
66564
+ content: [
66565
+ ...buildArtboardContentBlocks(artboardBase64s),
66566
+ {
66567
+ type: "image",
66568
+ source: { type: "base64", media_type: "image/png", data: screenshotBase64 }
66569
+ },
66570
+ {
66571
+ type: "text",
66572
+ text: "This is the actual screenshot. List all design system violations as JSON."
66573
+ }
66574
+ ]
66575
+ }
66576
+ ];
66577
+ }
66550
66578
  var downscaleAllBuffers = (0, import_neverthrow28.fromAsyncThrowable)(
66551
66579
  downscaleAll,
66552
66580
  (cause) => ({ type: "CLAUDE_API_FAILED", cause })
@@ -66601,6 +66629,23 @@ function resolveExactMatchOutcome({
66601
66629
  }
66602
66630
  return stepFinding ? { outcome: "fail", finding: stepFinding } : { outcome: "pass" };
66603
66631
  }
66632
+ async function collectDesignContextFindings(context) {
66633
+ let stepFinding;
66634
+ await compareWithDesignContext(context.screenshot, context.artboardBuffers).match(
66635
+ (findings) => {
66636
+ const mapped = findings.map((finding) => ({
66637
+ ...finding,
66638
+ screenshots: [context.screenshotPath]
66639
+ }));
66640
+ context.state.findings.push(...mapped);
66641
+ stepFinding = mapped[0];
66642
+ },
66643
+ (error48) => {
66644
+ pushClaudeError(context.state, { stepIndex: context.stepIndex, error: error48 });
66645
+ }
66646
+ );
66647
+ return { stepFinding };
66648
+ }
66604
66649
  async function compareArtboardExact({
66605
66650
  screenshot,
66606
66651
  artboard,
@@ -66625,23 +66670,6 @@ async function compareArtboardExact({
66625
66670
  });
66626
66671
  return resolveExactMatchOutcome({ stepFinding, isStale, ageDays });
66627
66672
  }
66628
- async function collectDesignContextFindings(context) {
66629
- let stepFinding;
66630
- await compareWithDesignContext(context.screenshot, context.artboardBuffers).match(
66631
- (findings) => {
66632
- const mapped = findings.map((finding) => ({
66633
- ...finding,
66634
- screenshots: [context.screenshotPath]
66635
- }));
66636
- context.state.findings.push(...mapped);
66637
- stepFinding = mapped[0];
66638
- },
66639
- (error48) => {
66640
- pushClaudeError(context.state, { stepIndex: context.stepIndex, error: error48 });
66641
- }
66642
- );
66643
- return { stepFinding };
66644
- }
66645
66673
  async function compareWithCandidates(context) {
66646
66674
  const { stepFinding } = await collectDesignContextFindings(context);
66647
66675
  if (stepFinding) {
@@ -66775,19 +66803,20 @@ function resolveStepEvent({ stepIndex, screenLabel, outcome }) {
66775
66803
  return { stepIndex, screenLabel, outcome: "pass" };
66776
66804
  }
66777
66805
  async function initArtboardNames({ designStore, config: config3, state }) {
66778
- state.artboardNamesPromise ??= designStore.listArtboards().match(
66779
- (names) => {
66780
- if (names.length === 0) {
66781
- emitProgress(config3, "no artboards found in designs directory");
66806
+ return state.getOrInitArtboardNames(
66807
+ async () => designStore.listArtboards().match(
66808
+ (names) => {
66809
+ if (names.length === 0) {
66810
+ emitProgress(config3, "no artboards found in designs directory");
66811
+ }
66812
+ return names;
66813
+ },
66814
+ (error48) => {
66815
+ emitProgress(config3, `Failed to list artboards: ${error48.type}`);
66816
+ return [];
66782
66817
  }
66783
- return names;
66784
- },
66785
- (error48) => {
66786
- emitProgress(config3, `Failed to list artboards: ${error48.type}`);
66787
- return [];
66788
- }
66818
+ )
66789
66819
  );
66790
- return state.artboardNamesPromise;
66791
66820
  }
66792
66821
  function readScreenshot(screenshotPath, stepIndex) {
66793
66822
  return import_neverthrow24.ResultAsync.fromThrowable(
@@ -66812,43 +66841,40 @@ async function processStep(context) {
66812
66841
  });
66813
66842
  config3.onStepComplete?.(resolveStepEvent({ stepIndex, screenLabel, outcome }));
66814
66843
  }
66844
+ var StepState = class {
66845
+ findings = [];
66846
+ errors = [];
66847
+ artboardNames = void 0;
66848
+ async getOrInitArtboardNames(init) {
66849
+ this.artboardNames ??= init();
66850
+ return this.artboardNames;
66851
+ }
66852
+ };
66815
66853
  var MAX_CONCURRENCY = 3;
66816
66854
  function applyTryResolve(state) {
66817
66855
  const { shouldResolve, result } = tryResolve(state);
66818
66856
  if (shouldResolve && state.resolve) {
66819
66857
  state.resolve(result);
66820
- state.resolve = void 0;
66858
+ state.clearResolve();
66821
66859
  }
66822
66860
  }
66823
66861
  function applyScheduleNext(state, context) {
66824
66862
  const { events, remainingQueue } = scheduleNext(state, MAX_CONCURRENCY);
66825
- state.queue = remainingQueue;
66863
+ state.setQueue(remainingQueue);
66826
66864
  for (const event of events) {
66827
- state.activeCount += 1;
66865
+ state.incrementActive();
66828
66866
  void processStep({
66829
66867
  event,
66830
66868
  designStore: context.designStore,
66831
66869
  config: context.config,
66832
66870
  state
66833
66871
  }).finally(() => {
66834
- state.activeCount -= 1;
66872
+ state.decrementActive();
66835
66873
  applyScheduleNext(state, context);
66836
66874
  applyTryResolve(state);
66837
66875
  });
66838
66876
  }
66839
66877
  }
66840
- function createRunnerState(config3) {
66841
- return {
66842
- findings: [],
66843
- errors: [],
66844
- activeCount: 0,
66845
- queue: [],
66846
- closed: false,
66847
- resolve: void 0,
66848
- signal: config3.signal,
66849
- artboardNamesPromise: void 0
66850
- };
66851
- }
66852
66878
  function buildInspector(state, context) {
66853
66879
  return {
66854
66880
  enqueue(event) {
@@ -66859,12 +66885,12 @@ function buildInspector(state, context) {
66859
66885
  applyScheduleNext(state, context);
66860
66886
  },
66861
66887
  close() {
66862
- state.closed = true;
66888
+ state.markClosed();
66863
66889
  applyTryResolve(state);
66864
66890
  },
66865
66891
  drain() {
66866
66892
  const { promise: promise2, resolve } = Promise.withResolvers();
66867
- state.resolve = resolve;
66893
+ state.setResolve(resolve);
66868
66894
  applyTryResolve(state);
66869
66895
  return (0, import_neverthrow23.fromSafePromise)(promise2);
66870
66896
  }
@@ -66872,9 +66898,38 @@ function buildInspector(state, context) {
66872
66898
  }
66873
66899
  function createInspector(config3, designStore) {
66874
66900
  const context = { designStore, config: config3 };
66875
- const state = createRunnerState(config3);
66901
+ const state = new RunnerState(config3.signal);
66876
66902
  return buildInspector(state, context);
66877
66903
  }
66904
+ var RunnerState = class extends StepState {
66905
+ activeCount = 0;
66906
+ queue = [];
66907
+ closed = false;
66908
+ resolve = void 0;
66909
+ signal;
66910
+ constructor(signal) {
66911
+ super();
66912
+ this.signal = signal;
66913
+ }
66914
+ setQueue(queue) {
66915
+ this.queue = queue;
66916
+ }
66917
+ incrementActive() {
66918
+ this.activeCount += 1;
66919
+ }
66920
+ decrementActive() {
66921
+ this.activeCount -= 1;
66922
+ }
66923
+ markClosed() {
66924
+ this.closed = true;
66925
+ }
66926
+ setResolve(resolve) {
66927
+ this.resolve = resolve;
66928
+ }
66929
+ clearResolve() {
66930
+ this.resolve = void 0;
66931
+ }
66932
+ };
66878
66933
  function parseMeta(raw) {
66879
66934
  const parsed = jsYaml.load(raw);
66880
66935
  if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
@@ -67210,7 +67265,7 @@ async function drainAfterExplorer(options) {
67210
67265
  return (0, import_neverthrow34.ok)({ artifacts: explorerResult.value, inspectorFindings });
67211
67266
  }
67212
67267
  function runExplorerAndDrain(options) {
67213
- return new import_neverthrow34.ResultAsync(drainAfterExplorer(options));
67268
+ return import_neverthrow34.ResultAsync.fromSafePromise(drainAfterExplorer(options)).andThen((result) => result);
67214
67269
  }
67215
67270
  function createDefaultMcpServers(udid) {
67216
67271
  return {
@@ -67717,8 +67772,8 @@ function buildAnalyseIdentity() {
67717
67772
  simulatorUdid: ""
67718
67773
  };
67719
67774
  }
67720
- function buildAnalyseEventHandler(identity, debug) {
67721
- const debugLogger = createDebugLogger({ enabled: debug });
67775
+ function buildAnalyseEventHandler(identity, isDebug) {
67776
+ const debugLogger = createDebugLogger({ enabled: isDebug });
67722
67777
  const baseHandler = (event) => {
67723
67778
  identity.display.onEvent({ ...event, itemId: ITEM_ID });
67724
67779
  };
@@ -69370,7 +69425,7 @@ function createPrompt(view) {
69370
69425
  effectScheduler.clearAll();
69371
69426
  throw error48;
69372
69427
  }).finally(() => {
69373
- cleanups.forEach((cleanup3) => cleanup3());
69428
+ cleanups.forEach((cleanup2) => cleanup2());
69374
69429
  screen.done({ clearContent: Boolean(context.clearPromptOnDone) });
69375
69430
  output.end();
69376
69431
  }).then(() => promise2), { cancel });
@@ -70664,43 +70719,6 @@ var DEFAULT_TERMINAL_WIDTH = 80;
70664
70719
  var CURSOR_MARGIN = 4;
70665
70720
  var ELLIPSIS_LENGTH = 3;
70666
70721
  var PAGE_SIZE = 15;
70667
- function dismissalKey(finding) {
70668
- return `${finding.flow}\0${finding.description}`;
70669
- }
70670
- function buildChoiceName(finding, state) {
70671
- const suffix = state === "dismissed" ? " [dismissed]" : state === "reconsidered" ? " [reconsidered]" : "";
70672
- const terminalWidth = process.stdout.columns > 0 ? process.stdout.columns : DEFAULT_TERMINAL_WIDTH;
70673
- const maxDescWidth = terminalWidth - CURSOR_MARGIN - FIXED_COLS_WIDTH - suffix.length;
70674
- const flow = source_default.bold(finding.flow.padEnd(FLOW_COL_WIDTH));
70675
- const triggerType = source_default.dim(finding.triggerType.padEnd(TRIGGER_COL_WIDTH));
70676
- const confidence = `${String(Math.round(finding.confidence * CONFIDENCE_PERCENT))}%`.padEnd(
70677
- CONFIDENCE_COL_WIDTH
70678
- );
70679
- const rawDescription = finding.description.split(".")[0] ?? finding.description;
70680
- const description = rawDescription.length > maxDescWidth ? `${rawDescription.slice(0, maxDescWidth - ELLIPSIS_LENGTH)}...` : rawDescription;
70681
- return `${flow} ${triggerType} ${confidence} ${description}${suffix ? source_default.dim(suffix) : ""}`;
70682
- }
70683
- function printDetail(finding) {
70684
- process.stdout.write(source_default.bold("\nDescription:\n"));
70685
- process.stdout.write(`${finding.description}
70686
- `);
70687
- process.stdout.write(source_default.bold("\nSteps:\n"));
70688
- for (const [index, step] of finding.steps.entries()) {
70689
- process.stdout.write(` ${String(index + 1)}. ${step}
70690
- `);
70691
- }
70692
- process.stdout.write(
70693
- `${source_default.bold("\nConfidence: ")}${String(Math.round(finding.confidence * CONFIDENCE_PERCENT))}%
70694
- `
70695
- );
70696
- if (finding.screenshots.length > 0) {
70697
- process.stdout.write(source_default.bold("\nScreenshots:\n"));
70698
- for (const screenshot of finding.screenshots) {
70699
- process.stdout.write(` ${screenshot}
70700
- `);
70701
- }
70702
- }
70703
- }
70704
70722
  async function handleFindingDetail(finding, state) {
70705
70723
  printDetail(finding);
70706
70724
  if (state === "dismissed") {
@@ -70791,6 +70809,43 @@ async function runLoopIteration(findings, state) {
70791
70809
  }
70792
70810
  return true;
70793
70811
  }
70812
+ function dismissalKey(finding) {
70813
+ return `${finding.flow}\0${finding.description}`;
70814
+ }
70815
+ function buildChoiceName(finding, state) {
70816
+ const suffix = state === "dismissed" ? " [dismissed]" : state === "reconsidered" ? " [reconsidered]" : "";
70817
+ const terminalWidth = process.stdout.columns > 0 ? process.stdout.columns : DEFAULT_TERMINAL_WIDTH;
70818
+ const maxDescWidth = terminalWidth - CURSOR_MARGIN - FIXED_COLS_WIDTH - suffix.length;
70819
+ const flow = source_default.bold(finding.flow.padEnd(FLOW_COL_WIDTH));
70820
+ const triggerType = source_default.dim(finding.triggerType.padEnd(TRIGGER_COL_WIDTH));
70821
+ const confidence = `${String(Math.round(finding.confidence * CONFIDENCE_PERCENT))}%`.padEnd(
70822
+ CONFIDENCE_COL_WIDTH
70823
+ );
70824
+ const rawDescription = finding.description.split(".")[0] ?? finding.description;
70825
+ const description = rawDescription.length > maxDescWidth ? `${rawDescription.slice(0, maxDescWidth - ELLIPSIS_LENGTH)}...` : rawDescription;
70826
+ return `${flow} ${triggerType} ${confidence} ${description}${suffix ? source_default.dim(suffix) : ""}`;
70827
+ }
70828
+ function printDetail(finding) {
70829
+ process.stdout.write(source_default.bold("\nDescription:\n"));
70830
+ process.stdout.write(`${finding.description}
70831
+ `);
70832
+ process.stdout.write(source_default.bold("\nSteps:\n"));
70833
+ for (const [index, step] of finding.steps.entries()) {
70834
+ process.stdout.write(` ${String(index + 1)}. ${step}
70835
+ `);
70836
+ }
70837
+ process.stdout.write(
70838
+ `${source_default.bold("\nConfidence: ")}${String(Math.round(finding.confidence * CONFIDENCE_PERCENT))}%
70839
+ `
70840
+ );
70841
+ if (finding.screenshots.length > 0) {
70842
+ process.stdout.write(source_default.bold("\nScreenshots:\n"));
70843
+ for (const screenshot of finding.screenshots) {
70844
+ process.stdout.write(` ${screenshot}
70845
+ `);
70846
+ }
70847
+ }
70848
+ }
70794
70849
  async function runInteractiveLoop(findings, existing) {
70795
70850
  const state = {
70796
70851
  dismissedKeys: new Set(existing.map((dismissal) => dismissalKey(dismissal.finding))),
@@ -70981,6 +71036,9 @@ var import_node_path17 = __toESM(require("node:path"), 1);
70981
71036
  // src/spec-slug.ts
70982
71037
  var import_node_path15 = __toESM(require("node:path"), 1);
70983
71038
  var SPECS_DIR = "specs";
71039
+ function stripExtensions(filename) {
71040
+ return filename.replace(/\.test\.md$/, "").replace(/\.[^.]+$/, "");
71041
+ }
70984
71042
  function deriveSpecSlug(specFilePath) {
70985
71043
  const parts = specFilePath.split(import_node_path15.default.sep);
70986
71044
  const specsIndex = parts.lastIndexOf(SPECS_DIR);
@@ -70992,9 +71050,6 @@ function deriveSpecSlug(specFilePath) {
70992
71050
  }
70993
71051
  return stripExtensions(import_node_path15.default.basename(specFilePath));
70994
71052
  }
70995
- function stripExtensions(filename) {
70996
- return filename.replace(/\.test\.md$/, "").replace(/\.[^.]+$/, "");
70997
- }
70998
71053
 
70999
71054
  // src/commands/spec-resolver.ts
71000
71055
  var import_node_fs8 = require("node:fs");
@@ -71027,16 +71082,6 @@ function parseTimeout(fields) {
71027
71082
  }
71028
71083
  return (0, import_neverthrow42.ok)(parsed);
71029
71084
  }
71030
- function parseSpecFrontmatter(content) {
71031
- return extractFrontmatterBlock(content).andThen((block) => {
71032
- const fields = parseYamlFields(block);
71033
- const feature = fields.get("feature");
71034
- if (feature === void 0) {
71035
- return (0, import_neverthrow42.err)({ type: "MISSING_FIELD", field: "feature" });
71036
- }
71037
- return parseTimeout(fields).map((timeout) => ({ feature, timeout }));
71038
- });
71039
- }
71040
71085
  function parseYamlFields(block) {
71041
71086
  const map3 = /* @__PURE__ */ new Map();
71042
71087
  for (const line of block.split("\n")) {
@@ -71052,6 +71097,16 @@ function parseYamlFields(block) {
71052
71097
  }
71053
71098
  return map3;
71054
71099
  }
71100
+ function parseSpecFrontmatter(content) {
71101
+ return extractFrontmatterBlock(content).andThen((block) => {
71102
+ const fields = parseYamlFields(block);
71103
+ const feature = fields.get("feature");
71104
+ if (feature === void 0) {
71105
+ return (0, import_neverthrow42.err)({ type: "MISSING_FIELD", field: "feature" });
71106
+ }
71107
+ return parseTimeout(fields).map((timeout) => ({ feature, timeout }));
71108
+ });
71109
+ }
71055
71110
 
71056
71111
  // src/commands/spec-resolver.ts
71057
71112
  var safeReadFile4 = (0, import_neverthrow43.fromThrowable)((filePath) => (0, import_node_fs8.readFileSync)(filePath, "utf8"));
@@ -71063,25 +71118,6 @@ var safeSelect = import_neverthrow43.ResultAsync.fromThrowable(
71063
71118
  esm_default11,
71064
71119
  (error48) => error48 instanceof Error && error48.name === "ExitPromptError" ? "cancelled" : "failed"
71065
71120
  );
71066
- function readAndParseSpec(absolutePath) {
71067
- const readResult = safeReadFile4(absolutePath);
71068
- if (readResult.isErr()) {
71069
- const isEnoent3 = readResult.error.code === "ENOENT";
71070
- const message = isEnoent3 ? `Spec file not found: ${absolutePath}` : `Failed to read spec file: ${absolutePath}`;
71071
- process.stderr.write(`${message}
71072
- `);
71073
- process.exit(1);
71074
- return;
71075
- }
71076
- const frontmatterResult = parseSpecFrontmatter(readResult.value);
71077
- if (frontmatterResult.isErr()) {
71078
- process.stderr.write(`Invalid spec frontmatter: ${frontmatterResult.error.type}
71079
- `);
71080
- process.exit(1);
71081
- return;
71082
- }
71083
- return frontmatterResult.value;
71084
- }
71085
71121
  function findSpecFiles(xqaDirectory) {
71086
71122
  const specsDirectory = import_node_path16.default.join(xqaDirectory, "specs");
71087
71123
  return safeReaddir(specsDirectory).unwrapOr([]).filter((file2) => file2.endsWith(".test.md")).map((file2) => import_node_path16.default.join(specsDirectory, file2));
@@ -71104,6 +71140,25 @@ async function promptForSpec(specFiles, xqaDirectory) {
71104
71140
  }
71105
71141
  return result.value;
71106
71142
  }
71143
+ function readAndParseSpec(absolutePath) {
71144
+ const readResult = safeReadFile4(absolutePath);
71145
+ if (readResult.isErr()) {
71146
+ const isEnoent3 = readResult.error.code === "ENOENT";
71147
+ const message = isEnoent3 ? `Spec file not found: ${absolutePath}` : `Failed to read spec file: ${absolutePath}`;
71148
+ process.stderr.write(`${message}
71149
+ `);
71150
+ process.exit(1);
71151
+ return;
71152
+ }
71153
+ const frontmatterResult = parseSpecFrontmatter(readResult.value);
71154
+ if (frontmatterResult.isErr()) {
71155
+ process.stderr.write(`Invalid spec frontmatter: ${frontmatterResult.error.type}
71156
+ `);
71157
+ process.exit(1);
71158
+ return;
71159
+ }
71160
+ return frontmatterResult.value;
71161
+ }
71107
71162
  async function resolveSpecFile(specFile, xqaDirectory) {
71108
71163
  if (specFile !== void 0) {
71109
71164
  return specFile;
@@ -75567,17 +75622,6 @@ function deriveSpecName(filePath) {
75567
75622
  function deriveSpecId(name) {
75568
75623
  return `spec-${name.replaceAll("/", "__")}`;
75569
75624
  }
75570
- function buildSpecItems(filePaths) {
75571
- return [...new Set(filePaths)].map((filePath) => {
75572
- const name = deriveSpecName(filePath);
75573
- return {
75574
- type: "spec",
75575
- id: deriveSpecId(name),
75576
- name,
75577
- specPath: filePath
75578
- };
75579
- });
75580
- }
75581
75625
  function buildFreestyleItem(entry, index) {
75582
75626
  const position = String(index);
75583
75627
  const label = String(index + 1);
@@ -75592,6 +75636,17 @@ function buildFreestyleItem(entry, index) {
75592
75636
  }
75593
75637
  return base;
75594
75638
  }
75639
+ function buildSpecItems(filePaths) {
75640
+ return [...new Set(filePaths)].map((filePath) => {
75641
+ const name = deriveSpecName(filePath);
75642
+ return {
75643
+ type: "spec",
75644
+ id: deriveSpecId(name),
75645
+ name,
75646
+ specPath: filePath
75647
+ };
75648
+ });
75649
+ }
75595
75650
  function buildFreestyleItems(entries) {
75596
75651
  return entries.map((entry, index) => buildFreestyleItem(entry, index));
75597
75652
  }
@@ -75649,18 +75704,18 @@ var PriorityQueue = class {
75649
75704
  };
75650
75705
 
75651
75706
  // src/suite/shell/item-result-builder.ts
75652
- function getErrorMessage(cause) {
75653
- if (cause instanceof Error) {
75654
- return cause.message;
75655
- }
75656
- return String(cause);
75657
- }
75658
75707
  function getSpecPath(item) {
75659
75708
  if (item.type === "spec") {
75660
75709
  return item.specPath;
75661
75710
  }
75662
75711
  return null;
75663
75712
  }
75713
+ function getErrorMessage(cause) {
75714
+ if (cause instanceof Error) {
75715
+ return cause.message;
75716
+ }
75717
+ return String(cause);
75718
+ }
75664
75719
  function buildCompletedItem(context, result) {
75665
75720
  return {
75666
75721
  id: context.item.id,
@@ -75721,14 +75776,6 @@ function buildAbortedItem(item, simulatorUdid) {
75721
75776
  }
75722
75777
 
75723
75778
  // src/suite/shell/item-result-recorder.ts
75724
- function buildNotifyBase(itemContext) {
75725
- return {
75726
- itemId: itemContext.item.id,
75727
- itemName: itemContext.item.name,
75728
- durationMs: itemContext.durationMs,
75729
- at: Date.now()
75730
- };
75731
- }
75732
75779
  function isTimeoutError(error48) {
75733
75780
  return error48.type.toUpperCase().includes("TIMEOUT");
75734
75781
  }
@@ -75752,6 +75799,14 @@ function notifyItemResult(context, observer) {
75752
75799
  error: getErrorMessage(context.result.error.cause)
75753
75800
  });
75754
75801
  }
75802
+ function buildNotifyBase(itemContext) {
75803
+ return {
75804
+ itemId: itemContext.item.id,
75805
+ itemName: itemContext.item.name,
75806
+ durationMs: itemContext.durationMs,
75807
+ at: Date.now()
75808
+ };
75809
+ }
75755
75810
  function recordItemResult(input) {
75756
75811
  const { context, results, observer } = input;
75757
75812
  const { itemContext, result } = context;
@@ -75836,96 +75891,96 @@ function buildHookEnv(input) {
75836
75891
  // src/suite/shell/hook-runner.ts
75837
75892
  var import_node_child_process6 = require("node:child_process");
75838
75893
  var import_neverthrow50 = __toESM(require_index_cjs(), 1);
75839
- var noop2 = () => void 0;
75840
- function cleanup2(context) {
75841
- clearTimeout(context.timeoutHandle);
75842
- context.signal.removeEventListener("abort", context.onAbort);
75843
- }
75844
- function settle(context, outcome) {
75845
- if (context.settled.value) {
75846
- return;
75847
- }
75848
- context.settled.value = true;
75849
- cleanup2(context);
75850
- context.deferred.resolve(outcome);
75851
- }
75852
75894
  function makeDeferred() {
75853
75895
  const raw = Promise.withResolvers();
75854
75896
  return { promise: raw.promise, resolve: raw.resolve };
75855
75897
  }
75856
- function collectStderr(child, stderrReference) {
75857
- if (child.stderr === null) {
75858
- return;
75898
+ async function spawnHook(options) {
75899
+ const runtime = new HookRuntime(options);
75900
+ return runtime.start();
75901
+ }
75902
+ var HookRuntime = class {
75903
+ child;
75904
+ signal;
75905
+ deferred;
75906
+ timeoutMs;
75907
+ stderrBuffer = "";
75908
+ settled = false;
75909
+ timeoutHandle;
75910
+ onAbort;
75911
+ constructor(options) {
75912
+ const { script, cwd, env: env2, baseEnv, nodeExecPath, timeoutMs, signal } = options;
75913
+ this.child = (0, import_node_child_process6.spawn)(nodeExecPath, [script], {
75914
+ cwd,
75915
+ env: { ...baseEnv, ...env2 },
75916
+ stdio: ["ignore", "inherit", "pipe"]
75917
+ });
75918
+ this.signal = signal;
75919
+ this.timeoutMs = timeoutMs;
75920
+ this.deferred = makeDeferred();
75921
+ this.onAbort = () => {
75922
+ this.child.kill("SIGTERM");
75923
+ this.settle((0, import_neverthrow50.err)({ type: "HOOK_ABORTED" }));
75924
+ };
75859
75925
  }
75860
- child.stderr.on("data", (chunk) => {
75861
- stderrReference.value += chunk.toString();
75862
- });
75863
- }
75864
- function buildOnAbort(context) {
75865
- return () => {
75866
- context.child.kill("SIGTERM");
75867
- settle(context, (0, import_neverthrow50.err)({ type: "HOOK_ABORTED" }));
75868
- };
75869
- }
75870
- function attachChildListeners(context) {
75871
- const { child, stderrReference } = context;
75872
- child.on("error", (cause) => {
75873
- settle(context, (0, import_neverthrow50.err)({ type: "HOOK_SPAWN_FAILED", cause }));
75874
- });
75875
- child.on("exit", (code) => {
75876
- if (code === 0) {
75877
- settle(context, (0, import_neverthrow50.ok)());
75926
+ async start() {
75927
+ this.collectStderr();
75928
+ this.attachTimeout();
75929
+ if (this.signal.aborted) {
75930
+ this.onAbort();
75931
+ return this.deferred.promise;
75932
+ }
75933
+ this.signal.addEventListener("abort", this.onAbort, { once: true });
75934
+ this.attachChildListeners();
75935
+ return this.deferred.promise;
75936
+ }
75937
+ collectStderr() {
75938
+ if (this.child.stderr === null) {
75878
75939
  return;
75879
75940
  }
75880
- settle(
75881
- context,
75882
- (0, import_neverthrow50.err)({
75883
- type: "HOOK_EXIT_NONZERO",
75884
- code: code ?? -1,
75885
- stderr: stderrReference.value
75886
- })
75887
- );
75888
- });
75889
- }
75890
- function attachTimeout(context, timeoutMs) {
75891
- clearTimeout(context.timeoutHandle);
75892
- context.timeoutHandle = setTimeout(() => {
75893
- context.child.kill("SIGTERM");
75894
- settle(context, (0, import_neverthrow50.err)({ type: "HOOK_TIMEOUT", timeoutMs }));
75895
- }, timeoutMs);
75896
- }
75897
- function buildContext2(options) {
75898
- const { script, cwd, env: env2, baseEnv, nodeExecPath } = options;
75899
- const deferred = makeDeferred();
75900
- const stderrReference = { value: "" };
75901
- const child = (0, import_node_child_process6.spawn)(nodeExecPath, [script], {
75902
- cwd,
75903
- env: { ...baseEnv, ...env2 },
75904
- stdio: ["ignore", "inherit", "pipe"]
75905
- });
75906
- collectStderr(child, stderrReference);
75907
- return {
75908
- child,
75909
- signal: options.signal,
75910
- timeoutHandle: setTimeout(noop2, 0),
75911
- onAbort: noop2,
75912
- deferred,
75913
- stderrReference,
75914
- settled: { value: false }
75915
- };
75916
- }
75917
- async function spawnHook(options) {
75918
- const context = buildContext2(options);
75919
- attachTimeout(context, options.timeoutMs);
75920
- context.onAbort = buildOnAbort(context);
75921
- if (options.signal.aborted) {
75922
- context.onAbort();
75923
- return context.deferred.promise;
75941
+ this.child.stderr.on("data", (chunk) => {
75942
+ this.stderrBuffer += chunk.toString();
75943
+ });
75924
75944
  }
75925
- options.signal.addEventListener("abort", context.onAbort, { once: true });
75926
- attachChildListeners(context);
75927
- return context.deferred.promise;
75928
- }
75945
+ attachTimeout() {
75946
+ this.timeoutHandle = setTimeout(() => {
75947
+ this.child.kill("SIGTERM");
75948
+ this.settle((0, import_neverthrow50.err)({ type: "HOOK_TIMEOUT", timeoutMs: this.timeoutMs }));
75949
+ }, this.timeoutMs);
75950
+ }
75951
+ attachChildListeners() {
75952
+ this.child.on("error", (cause) => {
75953
+ this.settle((0, import_neverthrow50.err)({ type: "HOOK_SPAWN_FAILED", cause }));
75954
+ });
75955
+ this.child.on("exit", (code) => {
75956
+ if (code === 0) {
75957
+ this.settle((0, import_neverthrow50.ok)());
75958
+ return;
75959
+ }
75960
+ this.settle(
75961
+ (0, import_neverthrow50.err)({
75962
+ type: "HOOK_EXIT_NONZERO",
75963
+ code: code ?? -1,
75964
+ stderr: this.stderrBuffer
75965
+ })
75966
+ );
75967
+ });
75968
+ }
75969
+ settle(outcome) {
75970
+ if (this.settled) {
75971
+ return;
75972
+ }
75973
+ this.settled = true;
75974
+ this.cleanup();
75975
+ this.deferred.resolve(outcome);
75976
+ }
75977
+ cleanup() {
75978
+ if (this.timeoutHandle !== void 0) {
75979
+ clearTimeout(this.timeoutHandle);
75980
+ }
75981
+ this.signal.removeEventListener("abort", this.onAbort);
75982
+ }
75983
+ };
75929
75984
  var safeSpawn = import_neverthrow50.ResultAsync.fromThrowable(
75930
75985
  spawnHook,
75931
75986
  (cause) => ({ type: "HOOK_SPAWN_FAILED", cause })
@@ -76122,31 +76177,29 @@ function resolveFreestyleTimeout(item, config3) {
76122
76177
  }
76123
76178
  return config3.QA_EXPLORE_TIMEOUT_SECONDS ?? DEFAULT_FREESTYLE_TIMEOUT_SECONDS2;
76124
76179
  }
76125
- function buildFreestyleExplorerConfig(input) {
76126
- const { item, context, simulatorUdid } = input;
76127
- const { config: config3, date: date5, appContext } = context;
76128
- return {
76129
- mode: "freestyle",
76130
- date: date5,
76131
- mcpServers: createDefaultMcpServers(simulatorUdid),
76132
- allowedTools: ALLOWED_TOOLS,
76133
- timeoutMs: resolveFreestyleTimeout(item, config3) * MS_PER_SECOND4,
76134
- appContext: composeAppContext([buildDeviceInstruction(simulatorUdid), appContext, item.prompt]),
76135
- buildEnv: config3.QA_BUILD_ENV,
76136
- cwd: ensureWorkerCwd(simulatorUdid),
76137
- record: true
76138
- };
76139
- }
76140
76180
  function buildFreestylePipelineConfig(input) {
76141
76181
  const { item, context, signal, simulatorUdid, onEvent } = input;
76142
- const { xqaDirectory, runId } = context;
76182
+ const { config: config3, xqaDirectory, runId, date: date5, appContext } = context;
76143
76183
  return {
76144
76184
  outputDir: import_node_path21.default.join(xqaDirectory, "output", item.id),
76145
76185
  runId,
76146
76186
  simulatorUdid,
76147
76187
  signal,
76148
76188
  onEvent,
76149
- explorer: buildFreestyleExplorerConfig(input)
76189
+ explorer: {
76190
+ mode: "freestyle",
76191
+ date: date5,
76192
+ mcpServers: createDefaultMcpServers(simulatorUdid),
76193
+ allowedTools: ALLOWED_TOOLS,
76194
+ timeoutMs: resolveFreestyleTimeout(item, config3) * MS_PER_SECOND4,
76195
+ appContext: composeAppContext([
76196
+ buildDeviceInstruction(simulatorUdid),
76197
+ appContext,
76198
+ item.prompt
76199
+ ]),
76200
+ buildEnv: config3.QA_BUILD_ENV,
76201
+ cwd: ensureWorkerCwd(simulatorUdid)
76202
+ }
76150
76203
  };
76151
76204
  }
76152
76205
  function executeFreestyleItem(input) {
@@ -76168,8 +76221,7 @@ function buildSpecPipelineConfig(input) {
76168
76221
  allowedTools: ALLOWED_TOOLS,
76169
76222
  appContext: composeAppContext([buildDeviceInstruction(simulatorUdid), appContext]),
76170
76223
  buildEnv: config3.QA_BUILD_ENV,
76171
- cwd: ensureWorkerCwd(simulatorUdid),
76172
- record: true
76224
+ cwd: ensureWorkerCwd(simulatorUdid)
76173
76225
  }
76174
76226
  };
76175
76227
  }
@@ -76416,12 +76468,28 @@ async function runSuite(input) {
76416
76468
  aborted: input.signal.aborted
76417
76469
  });
76418
76470
  }
76471
+ async function resolveAndFilterItems(input) {
76472
+ const resolvedRaw = await resolveWorkItems(input);
76473
+ if (resolvedRaw === void 0) {
76474
+ return;
76475
+ }
76476
+ if (input.only === void 0) {
76477
+ return resolvedRaw;
76478
+ }
76479
+ const filtered = resolvedRaw.items.filter((item) => item.id === input.only);
76480
+ if (filtered.length === 0) {
76481
+ process.stderr.write(`No work item matched id: ${input.only}
76482
+ `);
76483
+ return;
76484
+ }
76485
+ return { ...resolvedRaw, items: filtered };
76486
+ }
76419
76487
  async function runSuiteCommand(input) {
76420
76488
  const preflightExit = await runPreflightChecks();
76421
76489
  if (preflightExit !== void 0) {
76422
76490
  return preflightExit;
76423
76491
  }
76424
- const resolved = await resolveWorkItems(input);
76492
+ const resolved = await resolveAndFilterItems(input);
76425
76493
  if (resolved === void 0) {
76426
76494
  return 1;
76427
76495
  }
@@ -76460,7 +76528,7 @@ function resolveXqaDirectory() {
76460
76528
  return result.value;
76461
76529
  }
76462
76530
  var program2 = new Command();
76463
- program2.name("xqa").description("AI-powered QA agent CLI").version(`${"1.15.1"}${false ? ` (dev build +${"8f8a210"})` : ""}`);
76531
+ program2.name("xqa").description("AI-powered QA agent CLI").version(`${"1.16.0"}${false ? ` (dev build +${"3af4493"})` : ""}`);
76464
76532
  program2.command("init").description("Initialize a new xqa project in the current directory").action(() => {
76465
76533
  runInitCommand();
76466
76534
  });
@@ -76516,11 +76584,15 @@ program2.command("spec").description("Run the explorer agent against a spec file
76516
76584
  { config: config2, xqaDirectory }
76517
76585
  );
76518
76586
  });
76519
- 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) => {
76587
+ 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("--only <id>", "Run only the work item with the given id (requires --suite)").option("--debug", "Log timing and event details to stderr").action(async (options) => {
76520
76588
  if (options.suite === void 0 === (options.spec === void 0)) {
76521
76589
  process.stderr.write("Exactly one of --suite or --spec must be provided\n");
76522
76590
  process.exit(1);
76523
76591
  }
76592
+ if (options.only !== void 0 && options.suite === void 0) {
76593
+ process.stderr.write("--only requires --suite\n");
76594
+ process.exit(1);
76595
+ }
76524
76596
  const xqaDirectory = resolveXqaDirectory();
76525
76597
  const mode = options.suite === void 0 ? { type: "spec", globs: options.spec ?? [] } : { type: "suite", name: options.suite };
76526
76598
  const exitCode = await runSuiteCommand({
@@ -76528,7 +76600,8 @@ program2.command("run").description("Run a test suite or a set of spec files").o
76528
76600
  xqaDirectory,
76529
76601
  config: config2,
76530
76602
  debug: options.debug ?? false,
76531
- signal: controller.signal
76603
+ signal: controller.signal,
76604
+ only: options.only
76532
76605
  });
76533
76606
  process.exit(exitCode);
76534
76607
  });