@exodus/xqa 1.2.3 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/xqa.cjs CHANGED
@@ -3587,7 +3587,7 @@ var require_index_cjs = __commonJS({
3587
3587
  }, reject);
3588
3588
  }
3589
3589
  }
3590
- var ResultAsync8 = class _ResultAsync {
3590
+ var ResultAsync9 = class _ResultAsync {
3591
3591
  constructor(res) {
3592
3592
  this._promise = res;
3593
3593
  }
@@ -3726,14 +3726,14 @@ var require_index_cjs = __commonJS({
3726
3726
  }
3727
3727
  };
3728
3728
  function okAsync8(value) {
3729
- return new ResultAsync8(Promise.resolve(new Ok(value)));
3729
+ return new ResultAsync9(Promise.resolve(new Ok(value)));
3730
3730
  }
3731
3731
  function errAsync7(err17) {
3732
- return new ResultAsync8(Promise.resolve(new Err(err17)));
3732
+ return new ResultAsync9(Promise.resolve(new Err(err17)));
3733
3733
  }
3734
- var fromPromise = ResultAsync8.fromPromise;
3735
- var fromSafePromise2 = ResultAsync8.fromSafePromise;
3736
- var fromAsyncThrowable9 = ResultAsync8.fromThrowable;
3734
+ var fromPromise = ResultAsync9.fromPromise;
3735
+ var fromSafePromise2 = ResultAsync9.fromSafePromise;
3736
+ var fromAsyncThrowable9 = ResultAsync9.fromThrowable;
3737
3737
  var combineResultList = (resultList) => {
3738
3738
  let acc = ok17([]);
3739
3739
  for (const result of resultList) {
@@ -3746,7 +3746,7 @@ var require_index_cjs = __commonJS({
3746
3746
  }
3747
3747
  return acc;
3748
3748
  };
3749
- var combineResultAsyncList = (asyncResultList) => ResultAsync8.fromSafePromise(Promise.all(asyncResultList)).andThen(combineResultList);
3749
+ var combineResultAsyncList = (asyncResultList) => ResultAsync9.fromSafePromise(Promise.all(asyncResultList)).andThen(combineResultList);
3750
3750
  var combineResultListWithAllErrors = (resultList) => {
3751
3751
  let acc = ok17([]);
3752
3752
  for (const result of resultList) {
@@ -3760,9 +3760,9 @@ var require_index_cjs = __commonJS({
3760
3760
  }
3761
3761
  return acc;
3762
3762
  };
3763
- var combineResultAsyncListWithAllErrors = (asyncResultList) => ResultAsync8.fromSafePromise(Promise.all(asyncResultList)).andThen(combineResultListWithAllErrors);
3763
+ var combineResultAsyncListWithAllErrors = (asyncResultList) => ResultAsync9.fromSafePromise(Promise.all(asyncResultList)).andThen(combineResultListWithAllErrors);
3764
3764
  exports2.Result = void 0;
3765
- (function(Result) {
3765
+ (function(Result3) {
3766
3766
  function fromThrowable11(fn, errorFn) {
3767
3767
  return (...args) => {
3768
3768
  try {
@@ -3773,15 +3773,15 @@ var require_index_cjs = __commonJS({
3773
3773
  }
3774
3774
  };
3775
3775
  }
3776
- Result.fromThrowable = fromThrowable11;
3776
+ Result3.fromThrowable = fromThrowable11;
3777
3777
  function combine(resultList) {
3778
3778
  return combineResultList(resultList);
3779
3779
  }
3780
- Result.combine = combine;
3780
+ Result3.combine = combine;
3781
3781
  function combineWithAllErrors(resultList) {
3782
3782
  return combineResultListWithAllErrors(resultList);
3783
3783
  }
3784
- Result.combineWithAllErrors = combineWithAllErrors;
3784
+ Result3.combineWithAllErrors = combineWithAllErrors;
3785
3785
  })(exports2.Result || (exports2.Result = {}));
3786
3786
  function ok17(value) {
3787
3787
  return new Ok(value);
@@ -3792,7 +3792,7 @@ var require_index_cjs = __commonJS({
3792
3792
  function safeTry(body) {
3793
3793
  const n3 = body().next();
3794
3794
  if (n3 instanceof Promise) {
3795
- return new ResultAsync8(n3.then((r3) => r3.value));
3795
+ return new ResultAsync9(n3.then((r3) => r3.value));
3796
3796
  }
3797
3797
  return n3.value;
3798
3798
  }
@@ -3843,7 +3843,7 @@ var require_index_cjs = __commonJS({
3843
3843
  return f6(this.value).map(() => this.value);
3844
3844
  }
3845
3845
  asyncMap(f6) {
3846
- return ResultAsync8.fromSafePromise(f6(this.value));
3846
+ return ResultAsync9.fromSafePromise(f6(this.value));
3847
3847
  }
3848
3848
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
3849
3849
  unwrapOr(_v) {
@@ -3947,7 +3947,7 @@ var require_index_cjs = __commonJS({
3947
3947
  var fromThrowable10 = exports2.Result.fromThrowable;
3948
3948
  exports2.Err = Err;
3949
3949
  exports2.Ok = Ok;
3950
- exports2.ResultAsync = ResultAsync8;
3950
+ exports2.ResultAsync = ResultAsync9;
3951
3951
  exports2.err = err16;
3952
3952
  exports2.errAsync = errAsync7;
3953
3953
  exports2.fromAsyncThrowable = fromAsyncThrowable9;
@@ -15864,6 +15864,18 @@ function formatMemoryElements(elements) {
15864
15864
  (element) => `${element.label} [${String(Math.round(element.confidence * PCT_MULTIPLIER))}%${element.phase === "after-scroll" ? "\u2193" : ""}]`
15865
15865
  ).join(", ");
15866
15866
  }
15867
+ var ALL_VERBOSE_CATEGORIES = /* @__PURE__ */ new Set([
15868
+ "prompt",
15869
+ "tools",
15870
+ "screen",
15871
+ "memory"
15872
+ ]);
15873
+ function isVerboseEnabled(config3, category) {
15874
+ if (config3 === void 0) {
15875
+ return false;
15876
+ }
15877
+ return config3.has(category);
15878
+ }
15867
15879
  var SCREEN_PREVIEW_LENGTH = 80;
15868
15880
  function write(line) {
15869
15881
  process.stderr.write(line + "\n");
@@ -15871,7 +15883,7 @@ function write(line) {
15871
15883
  function writePlainScreenState(event, verbose) {
15872
15884
  const preview = (event.snapshot.split("\n")[0] ?? "").slice(0, SCREEN_PREVIEW_LENGTH);
15873
15885
  write(`[${event.agent}] screen (${String(event.snapshot.length)} chars): ${preview}`);
15874
- if (verbose) {
15886
+ if (isVerboseEnabled(verbose, "screen")) {
15875
15887
  write(event.snapshot);
15876
15888
  }
15877
15889
  }
@@ -15879,7 +15891,7 @@ function writePlainScreenMemory(event, verbose) {
15879
15891
  write(
15880
15892
  `[${event.agent}] memory (${String(event.sessionsObserved)} sessions): ${formatMemoryElements(event.elements)}`
15881
15893
  );
15882
- if (verbose) {
15894
+ if (isVerboseEnabled(verbose, "memory")) {
15883
15895
  write(event.enrichedSnapshot);
15884
15896
  }
15885
15897
  }
@@ -15914,8 +15926,14 @@ function writePlainToolError(event) {
15914
15926
  write(`${prefix} error handling ${event.toolName}: ${line}`);
15915
15927
  }
15916
15928
  }
15929
+ function writePlainError(event) {
15930
+ write(`[${event.agent}] error: ${event.message}`);
15931
+ if (event.stack !== void 0) {
15932
+ write(event.stack);
15933
+ }
15934
+ }
15917
15935
  function writePlainToolResult(event, verbose) {
15918
- if (!verbose) {
15936
+ if (!isVerboseEnabled(verbose, "tools")) {
15919
15937
  return;
15920
15938
  }
15921
15939
  const prefix = `[${event.agent}]`;
@@ -15941,13 +15959,13 @@ function handlePlainToolEvent(event, verbose) {
15941
15959
  }
15942
15960
  }
15943
15961
  function writePlainSystemPrompt(event, verbose) {
15944
- if (!verbose) {
15962
+ if (!isVerboseEnabled(verbose, "prompt")) {
15945
15963
  return;
15946
15964
  }
15947
15965
  write(`[${event.agent}] system prompt:
15948
15966
  ${event.prompt}`);
15949
15967
  }
15950
- function dispatchPlainEventFirst(event, verbose) {
15968
+ function dispatchPlainNonVerboseFirst(event) {
15951
15969
  switch (event.type) {
15952
15970
  case "STAGE_START": {
15953
15971
  writePlainStageStart(event);
@@ -15961,16 +15979,19 @@ function dispatchPlainEventFirst(event, verbose) {
15961
15979
  writePlainThought(event);
15962
15980
  return;
15963
15981
  }
15964
- case "SCREEN_STATE": {
15965
- writePlainScreenState(event, verbose);
15966
- return;
15967
- }
15968
15982
  case "SCREENSHOT": {
15969
15983
  writePlainScreenshot(event);
15970
15984
  return;
15971
15985
  }
15972
15986
  }
15973
15987
  }
15988
+ function dispatchPlainEventFirst(event, verbose) {
15989
+ if (event.type === "SCREEN_STATE") {
15990
+ writePlainScreenState(event, verbose);
15991
+ return;
15992
+ }
15993
+ dispatchPlainNonVerboseFirst(event);
15994
+ }
15974
15995
  function dispatchPlainEventSecond(event, verbose) {
15975
15996
  switch (event.type) {
15976
15997
  case "SCREEN_MEMORY": {
@@ -15986,10 +16007,7 @@ function dispatchPlainEventSecond(event, verbose) {
15986
16007
  return;
15987
16008
  }
15988
16009
  case "ERROR": {
15989
- write(`[${event.agent}] error: ${event.message}`);
15990
- if (event.stack !== void 0) {
15991
- write(event.stack);
15992
- }
16010
+ writePlainError(event);
15993
16011
  return;
15994
16012
  }
15995
16013
  }
@@ -16054,14 +16072,14 @@ function createGitHubCIFormatter(write2) {
16054
16072
  for (const warning of flushWarnings(event.agent, warnings)) {
16055
16073
  write2(warning);
16056
16074
  }
16057
- handlePlain(event, false);
16075
+ handlePlain(event);
16058
16076
  return;
16059
16077
  }
16060
16078
  if (event.type === "INSPECTOR_STEP") {
16061
16079
  collectWarning(event, warnings);
16062
16080
  return;
16063
16081
  }
16064
- handlePlain(event, false);
16082
+ handlePlain(event);
16065
16083
  };
16066
16084
  }
16067
16085
  var CHALK_TRUECOLOR_LEVEL = 3;
@@ -16116,7 +16134,7 @@ function writePrettyMemory(event, context) {
16116
16134
  barLine(applyMemoryStyle(`\u25B8 memory (${String(event.sessionsObserved)} sessions): ${top}`)),
16117
16135
  context.state
16118
16136
  );
16119
- if (context.verbose) {
16137
+ if (isVerboseEnabled(context.verbose, "memory")) {
16120
16138
  for (const line of event.enrichedSnapshot.split("\n")) {
16121
16139
  writeLine(`${chalk2.dim(S_BAR)} ${applyMemoryStyle(line)}`, context.state);
16122
16140
  }
@@ -16136,7 +16154,7 @@ function writePrettyScreenState(snapshot, context) {
16136
16154
  barLine(applyMemoryStyle(`\u25B8 screen (${String(snapshot.length)} chars): ${preview}`)),
16137
16155
  context.state
16138
16156
  );
16139
- if (context.verbose) {
16157
+ if (isVerboseEnabled(context.verbose, "screen")) {
16140
16158
  for (const line of snapshot.split("\n")) {
16141
16159
  writeLine(`${chalk2.dim(S_BAR)} ${applyMemoryStyle(line)}`, context.state);
16142
16160
  }
@@ -16151,7 +16169,7 @@ function writePrettyError(event, state) {
16151
16169
  }
16152
16170
  }
16153
16171
  function writePrettySystemPrompt(event, context) {
16154
- if (!context.verbose) {
16172
+ if (!isVerboseEnabled(context.verbose, "prompt")) {
16155
16173
  return;
16156
16174
  }
16157
16175
  writeLine(barLine(applyThoughtStyle("\u25C6 system prompt")), context.state);
@@ -16566,7 +16584,7 @@ function buildToolArguments(input) {
16566
16584
  return Object.entries(input).filter(([key]) => !HIDDEN_TOOL_ARGS.has(key)).map(([key, value]) => `${key}: ${String(value)}`).join(", ");
16567
16585
  }
16568
16586
  function writeToolResult(event, context) {
16569
- if (context.verbose) {
16587
+ if (isVerboseEnabled(context.verbose, "tools")) {
16570
16588
  for (const line of event.result.split("\n")) {
16571
16589
  writeLine(`${chalk4.dim(S_BAR4)} ${applyToolStyle(line)}`, context.state);
16572
16590
  }
@@ -16697,7 +16715,7 @@ function resolveOutputMode() {
16697
16715
  }
16698
16716
  function createConsoleObserver(options) {
16699
16717
  const mode = options?.mode ?? resolveOutputMode();
16700
- const verbose = options?.verbose ?? false;
16718
+ const verbose = options?.verbose;
16701
16719
  if (mode === "tty") {
16702
16720
  return createHybridTtyRenderer({ verbose });
16703
16721
  }
@@ -48106,7 +48124,7 @@ function dismissalsPath(baseDirectory, override) {
48106
48124
  }
48107
48125
 
48108
48126
  // ../../packages/pipeline/dist/index.js
48109
- var import_neverthrow27 = __toESM(require_index_cjs(), 1);
48127
+ var import_neverthrow28 = __toESM(require_index_cjs(), 1);
48110
48128
  var import_promises14 = require("node:timers/promises");
48111
48129
 
48112
48130
  // ../../agents/analyser/dist/index.js
@@ -55763,10 +55781,10 @@ function runConsolidator(input, config3) {
55763
55781
  }
55764
55782
 
55765
55783
  // ../../packages/pipeline/dist/index.js
55766
- var import_neverthrow28 = __toESM(require_index_cjs(), 1);
55767
55784
  var import_neverthrow29 = __toESM(require_index_cjs(), 1);
55768
- var import_node_fs3 = require("node:fs");
55769
55785
  var import_neverthrow30 = __toESM(require_index_cjs(), 1);
55786
+ var import_node_fs3 = require("node:fs");
55787
+ var import_neverthrow31 = __toESM(require_index_cjs(), 1);
55770
55788
  var import_promises15 = require("node:timers/promises");
55771
55789
 
55772
55790
  // ../../agents/explorer/dist/index.js
@@ -55776,16 +55794,17 @@ var import_node_path4 = __toESM(require("node:path"), 1);
55776
55794
  var import_neverthrow14 = __toESM(require_index_cjs(), 1);
55777
55795
  var import_promises8 = require("node:fs/promises");
55778
55796
  var import_neverthrow15 = __toESM(require_index_cjs(), 1);
55779
- var import_promises9 = require("node:timers/promises");
55780
55797
  var import_neverthrow16 = __toESM(require_index_cjs(), 1);
55798
+ var import_promises9 = require("node:timers/promises");
55781
55799
  var import_neverthrow17 = __toESM(require_index_cjs(), 1);
55800
+ var import_neverthrow18 = __toESM(require_index_cjs(), 1);
55782
55801
  var import_promises10 = require("node:fs/promises");
55783
55802
  var import_node_path5 = __toESM(require("node:path"), 1);
55784
- var import_neverthrow18 = __toESM(require_index_cjs(), 1);
55803
+ var import_neverthrow19 = __toESM(require_index_cjs(), 1);
55785
55804
  var import_node_child_process4 = require("node:child_process");
55786
55805
  var import_promises11 = require("node:fs/promises");
55787
55806
  var import_node_path6 = __toESM(require("node:path"), 1);
55788
- var import_neverthrow19 = __toESM(require_index_cjs(), 1);
55807
+ var import_neverthrow20 = __toESM(require_index_cjs(), 1);
55789
55808
  async function runFfmpeg(arguments_) {
55790
55809
  const { promise: promise2, resolve, reject } = Promise.withResolvers();
55791
55810
  (0, import_node_child_process3.execFile)("ffmpeg", arguments_, (error48) => {
@@ -55883,6 +55902,299 @@ function speedUpVideo({
55883
55902
  () => outputPath
55884
55903
  );
55885
55904
  }
55905
+ var DEV_ENVIRONMENT_SECTION = `## Environment
55906
+
55907
+ This is a development build. Debug overlays and internal messages are expected artifacts \u2014 do not report them as findings.`;
55908
+ var WORKING_STATE_SECTION = `## Working State
55909
+
55910
+ At every reasoning step, maintain a mental ledger:
55911
+ - VISITED: screen names confirmed via \`view_ui\` this session
55912
+ - QUEUE: screen names seen as reachable but not yet explored \u2014 also seed from App Knowledge if present
55913
+ - PATH: your current navigation stack from root (e.g. Home > Settings > Privacy)
55914
+
55915
+ Consult the ledger before every action. Always prefer navigating to a QUEUE screen over a VISITED one.`;
55916
+ 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`;
55917
+ 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`;
55918
+ 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`;
55919
+ var WHAT_TO_TEST_SECTION = `## What to Test
55920
+
55921
+ Test navigation elements first, interactions second.
55922
+
55923
+ **Tier 1 \u2014 test every one, every time:**
55924
+ - Tab bar items, drawer menu items, hamburger menus
55925
+ - Back buttons, close buttons (X), modal dismiss controls
55926
+ - Bottom sheet handles and drag gestures
55927
+ - Navigation links and "Go to X" CTAs
55928
+
55929
+ **Tier 2 \u2014 test one representative per type per screen:**
55930
+ - Primary action buttons
55931
+ - Form inputs and toggles
55932
+ - Segmented controls and dropdowns
55933
+
55934
+ **Tier 3 \u2014 skip unless visibly broken:**
55935
+ - Static labels, decorative images, dividers
55936
+
55937
+ If an interaction produces no observable change, retry once before flagging.`;
55938
+ var DEAD_END_SECTION = `## Dead End and Modal Detection
55939
+
55940
+ **Dead end** \u2014 when \`view_ui\` shows no interactive exit affordance, first consult App Knowledge for gesture-based navigation on this screen, then attempt ALL of before emitting a finding: (1) any visible back/close button, (2) swipe from the left edge (back gesture), (3) swipe down (dismiss gesture). If all fail, emit a \`dead-end\` finding describing what was visible and what was attempted.
55941
+
55942
+ **Stuck modal** \u2014 when a modal or bottom sheet blocks the screen, attempt dismissal in order: (1) close/X button if present, (2) tap outside the modal, (3) swipe down, (4) swipe from the left edge. If all fail, emit a \`stuck-modal\` finding listing the modal, the screen it appeared on, and the methods attempted.`;
55943
+ var SPEC_WHAT_TO_TEST_SECTION = `## What to Test
55944
+
55945
+ Test only the elements and interactions described in the spec. Do not interact with elements outside the spec path.
55946
+
55947
+ If you observe obvious breakage while navigating to a spec step \u2014 a broken control, unexpected error, missing screen, or crash \u2014 flag it as a passive observation without stopping to investigate it.`;
55948
+ var SPEC_DEAD_END_SECTION = `## Dead End and Modal Detection
55949
+
55950
+ **Dead end** \u2014 if a spec step leaves the agent on a screen with no path to the next spec step, attempt: (1) any visible back/close button, (2) swipe from the left edge, (3) swipe down. If all fail, emit a \`dead-end\` finding and halt \u2014 do not attempt further exploration to recover.
55951
+
55952
+ **Stuck modal** \u2014 when a modal or bottom sheet blocks spec step execution, attempt dismissal in order: (1) close/X button if present, (2) tap outside the modal, (3) swipe down, (4) swipe from the left edge. If all fail, emit a \`stuck-modal\` finding listing the modal, the screen it appeared on, and the methods attempted.`;
55953
+ function buildContextSections(appContext, initialState) {
55954
+ return [
55955
+ appContext ? `## App Knowledge
55956
+
55957
+ ${appContext}` : void 0,
55958
+ initialState ? `## Initial State
55959
+
55960
+ ${initialState}` : void 0,
55961
+ WORKING_STATE_SECTION
55962
+ ].filter((section) => section !== void 0).join("\n\n");
55963
+ }
55964
+ var SPEC_RULES_SECTION = `## Rules
55965
+
55966
+ - ALWAYS call \`view_ui\` after every action before deciding what to do next \u2014 it is your only way to observe the screen
55967
+ - ${BACK_NAV_RULE}
55968
+ - Before selecting any action, prefer navigating to a QUEUE screen over re-exploring a VISITED one
55969
+ - ${STUCK_LOOP_RULE}
55970
+ - ${CLIPPED_ELEMENT_RULE}
55971
+ - 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
55972
+ - 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`;
55973
+ function buildSpecModeBody({
55974
+ specContent,
55975
+ contextBlock,
55976
+ environmentSection
55977
+ }) {
55978
+ 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.
55979
+
55980
+ Verify app against specs below.
55981
+
55982
+ ${contextBlock}
55983
+
55984
+ ${SPEC_RULES_SECTION}
55985
+
55986
+ ## Execution Strategy
55987
+
55988
+ Execute spec steps in strict sequence. Navigate by the shortest path to each step's target screen. Do not interact with any screen, element, or flow not required by the spec.
55989
+
55990
+ ${SPEC_WHAT_TO_TEST_SECTION}
55991
+
55992
+ ${SPEC_DEAD_END_SECTION}
55993
+
55994
+ ## Specs
55995
+
55996
+ ${specContent}${environmentSection}
55997
+
55998
+ ## Output
55999
+
56000
+ CRITICAL: Call \`set_output\` each time your findings change \u2014 when you discover something new, confirm a false positive, or revise a finding. Each call replaces the previous output entirely, so always pass the full current list. Do not reply in plain text.`;
56001
+ }
56002
+ var SPEC_MODE_TEMPLATE = (specContent, options) => {
56003
+ const contextBlock = buildContextSections(options.appContext, options.initialState);
56004
+ const environmentSection = options.buildEnv === "dev" ? `
56005
+
56006
+ ${DEV_ENVIRONMENT_SECTION}` : "";
56007
+ return buildSpecModeBody({ specContent, contextBlock, environmentSection });
56008
+ };
56009
+ var FREESTYLE_TEMPLATE = (options) => {
56010
+ const { appContext, initialState, buildEnv } = options ?? {};
56011
+ const contextBlock = buildContextSections(appContext, initialState);
56012
+ const environmentSection = buildEnv === "dev" ? `
56013
+
56014
+ ${DEV_ENVIRONMENT_SECTION}` : "";
56015
+ 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.
56016
+
56017
+ ${contextBlock}
56018
+
56019
+ ## Rules
56020
+
56021
+ - ALWAYS call \`view_ui\` after every action before deciding what to do next \u2014 it is your only way to observe the screen
56022
+ - ${BACK_NAV_RULE}
56023
+ - Before selecting any action, prefer navigating to a QUEUE screen over re-exploring a VISITED one
56024
+ - ${STUCK_LOOP_RULE}
56025
+ - ${CLIPPED_ELEMENT_RULE}
56026
+
56027
+ ## Exploration Strategy
56028
+
56029
+ Explore breadth-first: map all screens reachable from the current screen before descending into any one path. Visit all siblings at the current depth before going deeper.
56030
+
56031
+ ${WHAT_TO_TEST_SECTION}
56032
+
56033
+ ${DEAD_END_SECTION}${environmentSection}
56034
+
56035
+ ## Output
56036
+
56037
+ CRITICAL: Call \`set_output\` each time your findings change \u2014 when you discover something new, confirm a false positive, or revise a finding. Each call replaces the previous output entirely, so always pass the full current list. Do not reply in plain text.`;
56038
+ };
56039
+ function generateExplorerPrompt({
56040
+ mode,
56041
+ specs,
56042
+ appContext,
56043
+ initialState,
56044
+ buildEnv
56045
+ }) {
56046
+ return mode === "spec" ? buildSpecModePrompt(specs, { appContext, initialState, buildEnv }) : FREESTYLE_TEMPLATE({ appContext, initialState, buildEnv });
56047
+ }
56048
+ function renderStep(step, index) {
56049
+ const stepNumber = String(index + 1);
56050
+ const base = `${stepNumber}. ${step.action}`;
56051
+ return step.assertion === void 0 ? base : `${base} \u2192 ${step.assertion}`;
56052
+ }
56053
+ function collectAssertions(spec) {
56054
+ return spec.assertions;
56055
+ }
56056
+ function renderSpec(spec) {
56057
+ const stepsBlock = spec.steps.map((step, index) => renderStep(step, index)).join("\n");
56058
+ const allAssertions = collectAssertions(spec);
56059
+ const assertionsBlock = allAssertions.length > 0 ? `
56060
+
56061
+ **Assertions**
56062
+ ${allAssertions.map((assertion) => `- ${assertion}`).join("\n")}` : "";
56063
+ return `### ${spec.name}
56064
+
56065
+ **Setup**
56066
+ ${spec.setup}
56067
+
56068
+ **Steps**
56069
+ ${stepsBlock}${assertionsBlock}`;
56070
+ }
56071
+ function buildSpecModePrompt(specs, options) {
56072
+ const specContent = specs.map((spec) => renderSpec(spec)).join("\n\n---\n\n");
56073
+ return SPEC_MODE_TEMPLATE(specContent, options);
56074
+ }
56075
+ var FRONTMATTER_FENCE = "---";
56076
+ var INLINE_ASSERTION_DELIMITER = " \u2192 ";
56077
+ var NUMBERED_STEP_PREFIX = /^\d+\.\s+/;
56078
+ function parseTagsValue(value) {
56079
+ if (!value.startsWith("[") || !value.endsWith("]")) {
56080
+ return (0, import_neverthrow16.err)({ type: "MALFORMED_FRONTMATTER", cause: `Invalid tags format: ${value}` });
56081
+ }
56082
+ return (0, import_neverthrow16.ok)(
56083
+ value.slice(1, -1).split(",").map((tag) => tag.trim()).filter((tag) => tag.length > 0)
56084
+ );
56085
+ }
56086
+ function parseTimeoutValue(value) {
56087
+ const parsed = Number(value);
56088
+ if (Number.isNaN(parsed) || parsed <= 0) {
56089
+ return (0, import_neverthrow16.err)({ type: "MALFORMED_FRONTMATTER", cause: `Invalid timeout: ${value}` });
56090
+ }
56091
+ return (0, import_neverthrow16.ok)(parsed);
56092
+ }
56093
+ function parseFrontmatterLine(line) {
56094
+ const colonIndex = line.indexOf(":");
56095
+ if (colonIndex === -1) {
56096
+ return (0, import_neverthrow16.err)({ type: "MALFORMED_FRONTMATTER", cause: `Invalid line: ${line}` });
56097
+ }
56098
+ const key = line.slice(0, colonIndex).trim();
56099
+ const value = line.slice(colonIndex + 1).trim();
56100
+ switch (key) {
56101
+ case "description": {
56102
+ return (0, import_neverthrow16.ok)({ description: value });
56103
+ }
56104
+ case "tags": {
56105
+ return parseTagsValue(value).map((tags) => ({ tags }));
56106
+ }
56107
+ case "timeout": {
56108
+ return parseTimeoutValue(value).map((timeout) => ({ timeout }));
56109
+ }
56110
+ default: {
56111
+ return (0, import_neverthrow16.ok)({});
56112
+ }
56113
+ }
56114
+ }
56115
+ function normalizeFrontmatter(raw) {
56116
+ return {
56117
+ description: raw.description,
56118
+ tags: raw.tags ?? [],
56119
+ timeout: raw.timeout
56120
+ };
56121
+ }
56122
+ function emptyFrontmatter() {
56123
+ return { description: void 0, tags: [], timeout: void 0 };
56124
+ }
56125
+ function mergePartials(partials) {
56126
+ return Object.fromEntries(partials.flatMap((partial2) => Object.entries(partial2)));
56127
+ }
56128
+ function combineFrontmatterLines(lines) {
56129
+ const lineResults = lines.filter((line) => line.trim().length > 0).map((line) => parseFrontmatterLine(line));
56130
+ return import_neverthrow16.Result.combine(lineResults).map((partials) => mergePartials(partials)).map((merged) => normalizeFrontmatter(merged));
56131
+ }
56132
+ function extractFrontmatter(content) {
56133
+ const trimmed = content.trimStart();
56134
+ if (!trimmed.startsWith(FRONTMATTER_FENCE)) {
56135
+ return (0, import_neverthrow16.ok)({ frontmatter: emptyFrontmatter(), body: content });
56136
+ }
56137
+ const afterOpenFence = trimmed.slice(FRONTMATTER_FENCE.length);
56138
+ const closeIndex = afterOpenFence.indexOf(`
56139
+ ${FRONTMATTER_FENCE}`);
56140
+ if (closeIndex === -1) {
56141
+ return (0, import_neverthrow16.err)({ type: "MALFORMED_FRONTMATTER", cause: "Unclosed frontmatter fence" });
56142
+ }
56143
+ const rawFrontmatter = afterOpenFence.slice(0, closeIndex);
56144
+ const body = afterOpenFence.slice(closeIndex + FRONTMATTER_FENCE.length + 1);
56145
+ return combineFrontmatterLines(rawFrontmatter.split("\n")).map((frontmatter) => ({
56146
+ frontmatter,
56147
+ body
56148
+ }));
56149
+ }
56150
+ function splitIntoSections(body) {
56151
+ const parts = ("\n" + body).split(/\n## /).slice(1);
56152
+ return Object.fromEntries(
56153
+ parts.map((part) => {
56154
+ const newlineIndex = part.indexOf("\n");
56155
+ const heading = newlineIndex === -1 ? part.trim() : part.slice(0, newlineIndex).trim();
56156
+ const sectionContent = newlineIndex === -1 ? "" : part.slice(newlineIndex + 1).trim();
56157
+ return [heading, sectionContent];
56158
+ })
56159
+ );
56160
+ }
56161
+ function parseStep(line) {
56162
+ const withoutNumber = line.replace(NUMBERED_STEP_PREFIX, "");
56163
+ const delimiterIndex = withoutNumber.indexOf(INLINE_ASSERTION_DELIMITER);
56164
+ if (delimiterIndex === -1) {
56165
+ return { action: withoutNumber.trim(), assertion: void 0 };
56166
+ }
56167
+ return {
56168
+ action: withoutNumber.slice(0, delimiterIndex).trim(),
56169
+ assertion: withoutNumber.slice(delimiterIndex + INLINE_ASSERTION_DELIMITER.length).trim()
56170
+ };
56171
+ }
56172
+ function parseSteps(stepsSection) {
56173
+ return stepsSection.split("\n").filter((line) => NUMBERED_STEP_PREFIX.test(line)).map((line) => parseStep(line));
56174
+ }
56175
+ function parseAssertions(assertionsSection) {
56176
+ return assertionsSection.split("\n").filter((line) => line.startsWith("- ")).map((line) => line.slice(2).trim());
56177
+ }
56178
+ function parseTestSpec(name, content) {
56179
+ return extractFrontmatter(content).andThen(({ frontmatter, body }) => {
56180
+ const sections = splitIntoSections(body);
56181
+ const setup = sections.Setup;
56182
+ if (setup === void 0) {
56183
+ return (0, import_neverthrow16.err)({ type: "MISSING_SETUP_SECTION" });
56184
+ }
56185
+ const stepsSection = sections.Steps;
56186
+ if (stepsSection === void 0) {
56187
+ return (0, import_neverthrow16.err)({ type: "MISSING_STEPS_SECTION" });
56188
+ }
56189
+ return (0, import_neverthrow16.ok)({
56190
+ name,
56191
+ frontmatter,
56192
+ setup,
56193
+ steps: parseSteps(stepsSection),
56194
+ assertions: sections.Assertions === void 0 ? [] : parseAssertions(sections.Assertions)
56195
+ });
56196
+ });
56197
+ }
55886
56198
  var VIEW_UI_TOOL_NAME = "mcp__perception__view_ui";
55887
56199
  var VIEW_UI_DESCRIPTION = `Capture the current screen state: accessibility snapshot (element list with positions) and screenshot. Call this after every action to observe the result.
55888
56200
 
@@ -55898,7 +56210,7 @@ function deriveScreenLabel(tree, stepIndex) {
55898
56210
  }
55899
56211
  async function persistScreenshot(params) {
55900
56212
  const screenshotPath = import_node_path5.default.join(params.screenshotsDirectory, `${params.screenLabel}.png`);
55901
- const safeWriteFile = (0, import_neverthrow18.fromAsyncThrowable)(
56213
+ const safeWriteFile = (0, import_neverthrow19.fromAsyncThrowable)(
55902
56214
  import_promises10.writeFile,
55903
56215
  (cause) => ({ type: "WRITE_FAILED", cause })
55904
56216
  );
@@ -55963,7 +56275,7 @@ function buildSnapshotRecord(rawElements, { stepIndex, context }) {
55963
56275
  }
55964
56276
  async function handleScreenshotData(data, params) {
55965
56277
  if (params.context.screenshotsDir !== void 0) {
55966
- const safeWrite2 = (0, import_neverthrow18.fromAsyncThrowable)(
56278
+ const safeWrite2 = (0, import_neverthrow19.fromAsyncThrowable)(
55967
56279
  import_promises10.writeFile,
55968
56280
  (cause) => ({ type: "WRITE_FAILED", cause })
55969
56281
  );
@@ -56133,23 +56445,23 @@ function processMessage(message, state) {
56133
56445
  }
56134
56446
  if (message.type === "result") {
56135
56447
  if (message.subtype !== "success" && !state.timedOut.value && !state.aborted.value) {
56136
- return (0, import_neverthrow17.err)(message.errors.join("; "));
56448
+ return (0, import_neverthrow18.err)(message.errors.join("; "));
56137
56449
  }
56138
- return (0, import_neverthrow17.ok)(true);
56450
+ return (0, import_neverthrow18.ok)(true);
56139
56451
  }
56140
- return (0, import_neverthrow17.ok)(false);
56452
+ return (0, import_neverthrow18.ok)(false);
56141
56453
  }
56142
56454
  async function processMessages(queryRunner, state) {
56143
56455
  for await (const message of queryRunner) {
56144
56456
  const result = processMessage(message, state);
56145
56457
  if (result.isErr()) {
56146
- return (0, import_neverthrow17.err)(result.error);
56458
+ return (0, import_neverthrow18.err)(result.error);
56147
56459
  }
56148
56460
  if (result.value) {
56149
56461
  break;
56150
56462
  }
56151
56463
  }
56152
- return (0, import_neverthrow17.ok)(null);
56464
+ return (0, import_neverthrow18.ok)(null);
56153
56465
  }
56154
56466
  var MessageQueue = class {
56155
56467
  pending = [];
@@ -56409,7 +56721,7 @@ var INTERRUPT_DRAIN_TIMEOUT_MS = 1e4;
56409
56721
  async function interruptOrTimeout(queryRunner) {
56410
56722
  await Promise.race([queryRunner.interrupt(), (0, import_promises9.setTimeout)(INTERRUPT_DRAIN_TIMEOUT_MS)]);
56411
56723
  }
56412
- var safeInterruptOrTimeout = import_neverthrow16.ResultAsync.fromThrowable(
56724
+ var safeInterruptOrTimeout = import_neverthrow17.ResultAsync.fromThrowable(
56413
56725
  interruptOrTimeout,
56414
56726
  (error48) => error48
56415
56727
  );
@@ -56460,18 +56772,18 @@ function startQueryTimers(config3, context) {
56460
56772
  }
56461
56773
  function awaitMessagesAndResolve({ queryRunner, state }, { cleanup: cleanup2, getOutput }) {
56462
56774
  const messagesPromise = processMessages(queryRunner, state);
56463
- return import_neverthrow16.ResultAsync.fromPromise(messagesPromise, String).andThen((innerResult) => {
56775
+ return import_neverthrow17.ResultAsync.fromPromise(messagesPromise, String).andThen((innerResult) => {
56464
56776
  cleanup2();
56465
56777
  if (innerResult.isErr()) {
56466
- return (0, import_neverthrow16.err)(innerResult.error);
56778
+ return (0, import_neverthrow17.err)(innerResult.error);
56467
56779
  }
56468
- return (0, import_neverthrow16.ok)(getOutput());
56780
+ return (0, import_neverthrow17.ok)(getOutput());
56469
56781
  }).orElse((sdkError) => {
56470
56782
  cleanup2();
56471
56783
  if (!state.timedOut.value && !state.aborted.value) {
56472
- return (0, import_neverthrow16.err)(sdkError);
56784
+ return (0, import_neverthrow17.err)(sdkError);
56473
56785
  }
56474
- return (0, import_neverthrow16.ok)(getOutput());
56786
+ return (0, import_neverthrow17.ok)(getOutput());
56475
56787
  });
56476
56788
  }
56477
56789
  function executeQuery({
@@ -56485,9 +56797,9 @@ function executeQuery({
56485
56797
  message: { role: "user", content: prompt },
56486
56798
  parent_tool_use_id: null
56487
56799
  });
56488
- const queryRunnerResult = (0, import_neverthrow16.fromThrowable)(Qs, String)({ prompt: inputQueue, options });
56800
+ const queryRunnerResult = (0, import_neverthrow17.fromThrowable)(Qs, String)({ prompt: inputQueue, options });
56489
56801
  if (queryRunnerResult.isErr()) {
56490
- return (0, import_neverthrow16.errAsync)(queryRunnerResult.error);
56802
+ return (0, import_neverthrow17.errAsync)(queryRunnerResult.error);
56491
56803
  }
56492
56804
  const queryRunner = queryRunnerResult.value;
56493
56805
  const cleanup2 = startQueryTimers(config3, { state, queryRunner, inputQueue, linkedController });
@@ -56502,7 +56814,7 @@ function executeQuery({
56502
56814
  function runQuery(prompt, config3) {
56503
56815
  const outputTools = createOutputTool({ findings: external_exports.array(EXPLORER_FINDING_SCHEMA) });
56504
56816
  const setupPromise = setupQuery(config3, outputTools);
56505
- return import_neverthrow16.ResultAsync.fromPromise(setupPromise, String).andThen(
56817
+ return import_neverthrow17.ResultAsync.fromPromise(setupPromise, String).andThen(
56506
56818
  (setup) => executeQuery({ prompt, config: config3, outputTools, setup })
56507
56819
  );
56508
56820
  }
@@ -56549,25 +56861,25 @@ function direntToSpecEntry(directory, entry) {
56549
56861
  return [];
56550
56862
  }
56551
56863
  function scanDirectory(directory) {
56552
- const safeReaddir = (0, import_neverthrow19.fromAsyncThrowable)(
56864
+ const safeReaddir2 = (0, import_neverthrow20.fromAsyncThrowable)(
56553
56865
  async () => (0, import_promises11.readdir)(directory, { withFileTypes: true }),
56554
56866
  (cause) => ({ type: "DIR_READ_FAILED", dir: directory, cause })
56555
56867
  );
56556
- return safeReaddir().orElse((error48) => isNotFound(error48.cause) ? (0, import_neverthrow19.okAsync)([]) : (0, import_neverthrow19.errAsync)(error48)).map(
56868
+ return safeReaddir2().orElse((error48) => isNotFound(error48.cause) ? (0, import_neverthrow20.okAsync)([]) : (0, import_neverthrow20.errAsync)(error48)).map(
56557
56869
  (entries) => entries.flatMap((entry) => direntToSpecEntry(directory, entry))
56558
56870
  );
56559
56871
  }
56560
56872
  function readEntries(entries) {
56561
- return import_neverthrow19.ResultAsync.combine(entries.map((entry) => readEntry(entry))).map(
56873
+ return import_neverthrow20.ResultAsync.combine(entries.map((entry) => readEntry(entry))).map(
56562
56874
  (results) => results.flat()
56563
56875
  );
56564
56876
  }
56565
56877
  function readEntry(entry) {
56566
- const safeReadFile5 = (0, import_neverthrow19.fromAsyncThrowable)(
56878
+ const safeReadFile5 = (0, import_neverthrow20.fromAsyncThrowable)(
56567
56879
  async () => (0, import_promises11.readFile)(entry.path, "utf8"),
56568
56880
  (cause) => ({ type: "FILE_READ_FAILED", path: entry.path, cause })
56569
56881
  );
56570
- return safeReadFile5().map((content) => [{ name: entry.name, content }]).orElse((error48) => entry.required ? (0, import_neverthrow19.errAsync)(error48) : (0, import_neverthrow19.okAsync)([]));
56882
+ return safeReadFile5().map((content) => [{ name: entry.name, content }]).orElse((error48) => entry.required ? (0, import_neverthrow20.errAsync)(error48) : (0, import_neverthrow20.okAsync)([]));
56571
56883
  }
56572
56884
  function specNameFromPath(filePath) {
56573
56885
  const parts = filePath.split("/");
@@ -56583,132 +56895,6 @@ function filterByNames(specs, specNames) {
56583
56895
  }
56584
56896
  return specs.filter((spec) => specNames.includes(spec.name));
56585
56897
  }
56586
- var DEV_ENVIRONMENT_SECTION = `## Environment
56587
-
56588
- This is a development build. Debug overlays and internal messages are expected artifacts \u2014 do not report them as findings.`;
56589
- var WORKING_STATE_SECTION = `## Working State
56590
-
56591
- At every reasoning step, maintain a mental ledger:
56592
- - VISITED: screen names confirmed via \`view_ui\` this session
56593
- - QUEUE: screen names seen as reachable but not yet explored
56594
- - PATH: your current navigation stack from root (e.g. Home > Settings > Privacy)
56595
-
56596
- Consult the ledger before every action. Always prefer navigating to a QUEUE screen over a VISITED one.`;
56597
- 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`;
56598
- 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`;
56599
- 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`;
56600
- var WHAT_TO_TEST_SECTION = `## What to Test
56601
-
56602
- Test navigation elements first, interactions second.
56603
-
56604
- **Tier 1 \u2014 test every one, every time:**
56605
- - Tab bar items, drawer menu items, hamburger menus
56606
- - Back buttons, close buttons (X), modal dismiss controls
56607
- - Bottom sheet handles and drag gestures
56608
- - Navigation links and "Go to X" CTAs
56609
-
56610
- **Tier 2 \u2014 test one representative per type per screen:**
56611
- - Primary action buttons
56612
- - Form inputs and toggles
56613
- - Segmented controls and dropdowns
56614
-
56615
- **Tier 3 \u2014 skip unless visibly broken:**
56616
- - Static labels, decorative images, dividers
56617
-
56618
- If an interaction produces no observable change, retry once before flagging.`;
56619
- var DEAD_END_SECTION = `## Dead End and Modal Detection
56620
-
56621
- **Dead end** \u2014 when \`view_ui\` shows no interactive exit affordance, attempt ALL of before emitting a finding: (1) any visible back/close button, (2) swipe from the left edge (back gesture), (3) swipe down (dismiss gesture). If all fail, emit a \`dead-end\` finding describing what was visible and what was attempted.
56622
-
56623
- **Stuck modal** \u2014 when a modal or bottom sheet blocks the screen, attempt dismissal in order: (1) close/X button if present, (2) tap outside the modal, (3) swipe down, (4) swipe from the left edge. If all fail, emit a \`stuck-modal\` finding listing the modal, the screen it appeared on, and the methods attempted.`;
56624
- var SPEC_MODE_TEMPLATE = (specContent, options) => {
56625
- const appSection = options.userPrompt ? `## Application
56626
-
56627
- ${options.userPrompt}
56628
-
56629
- ` : "";
56630
- const environmentSection = options.buildEnv === "dev" ? `
56631
-
56632
- ${DEV_ENVIRONMENT_SECTION}` : "";
56633
- 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.
56634
-
56635
- Verify app against specs below.
56636
-
56637
- ${appSection}${WORKING_STATE_SECTION}
56638
-
56639
- ## Rules
56640
-
56641
- - ALWAYS call \`view_ui\` after every action before deciding what to do next \u2014 it is your only way to observe the screen
56642
- - ${BACK_NAV_RULE.slice(2)}
56643
- - Before selecting any action, prefer navigating to a QUEUE screen over re-exploring a VISITED one
56644
- - ${STUCK_LOOP_RULE.slice(2)}
56645
- - ${CLIPPED_ELEMENT_RULE.slice(2)}
56646
- - Each item in \`## Assertions\` is a mandatory pass/fail check \u2014 verify using \`view_ui\` first; fall back to \`screenshot\` only when no accessibility node is present
56647
-
56648
- ## Exploration Strategy
56649
-
56650
- Navigate to verify each spec's scenarios. When choosing how to reach a screen, prefer breadth-first paths \u2014 map sibling screens before going deeper into any one branch.
56651
-
56652
- ${WHAT_TO_TEST_SECTION}
56653
-
56654
- ${DEAD_END_SECTION}
56655
-
56656
- ## Specs
56657
-
56658
- ${specContent}${environmentSection}
56659
-
56660
- ## Output
56661
-
56662
- CRITICAL: Call \`set_output\` each time your findings change \u2014 when you discover something new, confirm a false positive, or revise a finding. Each call replaces the previous output entirely, so always pass the full current list. Do not reply in plain text.`;
56663
- };
56664
- var FREESTYLE_TEMPLATE = (options) => {
56665
- const { userPrompt, buildEnv } = options ?? {};
56666
- const appSection = userPrompt ? `## Application
56667
-
56668
- ${userPrompt}
56669
-
56670
- ` : "";
56671
- const environmentSection = buildEnv === "dev" ? `
56672
-
56673
- ${DEV_ENVIRONMENT_SECTION}` : "";
56674
- 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.
56675
-
56676
- ${appSection}${WORKING_STATE_SECTION}
56677
-
56678
- ## Rules
56679
-
56680
- - ALWAYS call \`view_ui\` after every action before deciding what to do next \u2014 it is your only way to observe the screen
56681
- - ${BACK_NAV_RULE.slice(2)}
56682
- - Before selecting any action, prefer navigating to a QUEUE screen over re-exploring a VISITED one
56683
- - ${STUCK_LOOP_RULE.slice(2)}
56684
- - ${CLIPPED_ELEMENT_RULE.slice(2)}
56685
-
56686
- ## Exploration Strategy
56687
-
56688
- Explore breadth-first: map all screens reachable from the current screen before descending into any one path. Visit all siblings at the current depth before going deeper.
56689
-
56690
- ${WHAT_TO_TEST_SECTION}
56691
-
56692
- ${DEAD_END_SECTION}${environmentSection}
56693
-
56694
- ## Output
56695
-
56696
- CRITICAL: Call \`set_output\` each time your findings change \u2014 when you discover something new, confirm a false positive, or revise a finding. Each call replaces the previous output entirely, so always pass the full current list. Do not reply in plain text.`;
56697
- };
56698
- function generateExplorerPrompt({
56699
- mode,
56700
- specs,
56701
- userPrompt,
56702
- buildEnv
56703
- }) {
56704
- return mode === "spec" ? buildSpecModePrompt(specs, { userPrompt, buildEnv }) : FREESTYLE_TEMPLATE({ userPrompt, buildEnv });
56705
- }
56706
- function buildSpecModePrompt(specs, options) {
56707
- const specContent = specs.map((spec) => `### ${spec.name}
56708
-
56709
- ${spec.content}`).join("\n\n---\n\n");
56710
- return SPEC_MODE_TEMPLATE(specContent, options);
56711
- }
56712
56898
  var SPEED_2X = 2;
56713
56899
  var SPEED_4X = 4;
56714
56900
  var ISO_DATE_LENGTH = 10;
@@ -56716,10 +56902,20 @@ function buildPrompt(safeConfig, specs) {
56716
56902
  return generateExplorerPrompt({
56717
56903
  mode: safeConfig.mode,
56718
56904
  specs,
56719
- userPrompt: safeConfig.userPrompt,
56905
+ appContext: safeConfig.appContext,
56906
+ initialState: safeConfig.initialState,
56720
56907
  buildEnv: safeConfig.buildEnv
56721
56908
  });
56722
56909
  }
56910
+ function parseSpecs(resolvedSpecs) {
56911
+ return import_neverthrow15.Result.combine(
56912
+ resolvedSpecs.map(
56913
+ (spec) => parseTestSpec(spec.name, spec.content).mapErr(
56914
+ (cause) => ({ type: "SPEC_PARSE_FAILED", specName: spec.name, cause })
56915
+ )
56916
+ )
56917
+ );
56918
+ }
56723
56919
  function toArtifacts(result, runPaths) {
56724
56920
  return {
56725
56921
  findings: result.findings,
@@ -56752,12 +56948,18 @@ function collectAndFinalize({
56752
56948
  return error48;
56753
56949
  });
56754
56950
  }
56951
+ function resolveAndParseSpecs(safeConfig) {
56952
+ if (safeConfig.mode === "freestyle") {
56953
+ return (0, import_neverthrow15.okAsync)([]);
56954
+ }
56955
+ return resolveSpecs(safeConfig).mapErr((cause) => ({ type: "SPEC_RESOLVE_FAILED", cause })).andThen((specs) => parseSpecs(specs));
56956
+ }
56755
56957
  function runPipeline({
56756
56958
  safeConfig,
56757
56959
  runPaths,
56758
56960
  start
56759
56961
  }) {
56760
- return resolveSpecs(safeConfig).mapErr((cause) => ({ type: "SPEC_RESOLVE_FAILED", cause })).map((specs) => buildPrompt(safeConfig, specs)).map((prompt) => {
56962
+ return resolveAndParseSpecs(safeConfig).map((parsedSpecs) => buildPrompt(safeConfig, parsedSpecs)).map((prompt) => {
56761
56963
  safeConfig.onEvent?.({ type: "SYSTEM_PROMPT", agent: "explorer", prompt });
56762
56964
  return prompt;
56763
56965
  }).andThen((prompt) => collectAndFinalize({ safeConfig, prompt, runPaths, start }));
@@ -56841,17 +57043,17 @@ function runWithRecording(handle, collectOutput) {
56841
57043
  }
56842
57044
 
56843
57045
  // ../../packages/pipeline/dist/index.js
56844
- var import_neverthrow31 = __toESM(require_index_cjs(), 1);
57046
+ var import_neverthrow32 = __toESM(require_index_cjs(), 1);
56845
57047
 
56846
57048
  // ../../agents/inspector/dist/index.js
56847
- var import_neverthrow20 = __toESM(require_index_cjs(), 1);
56848
- var import_promises12 = require("node:fs/promises");
56849
57049
  var import_neverthrow21 = __toESM(require_index_cjs(), 1);
57050
+ var import_promises12 = require("node:fs/promises");
56850
57051
  var import_neverthrow22 = __toESM(require_index_cjs(), 1);
56851
57052
  var import_neverthrow23 = __toESM(require_index_cjs(), 1);
56852
57053
  var import_neverthrow24 = __toESM(require_index_cjs(), 1);
56853
- var import_sharp2 = __toESM(require("sharp"), 1);
56854
57054
  var import_neverthrow25 = __toESM(require_index_cjs(), 1);
57055
+ var import_sharp2 = __toESM(require("sharp"), 1);
57056
+ var import_neverthrow26 = __toESM(require_index_cjs(), 1);
56855
57057
  var import_promises13 = require("node:fs/promises");
56856
57058
  var import_node_path7 = __toESM(require("node:path"), 1);
56857
57059
 
@@ -59480,7 +59682,7 @@ var jsYaml = {
59480
59682
  };
59481
59683
 
59482
59684
  // ../../agents/inspector/dist/index.js
59483
- var import_neverthrow26 = __toESM(require_index_cjs(), 1);
59685
+ var import_neverthrow27 = __toESM(require_index_cjs(), 1);
59484
59686
  var MS_PER_DAY = 864e5;
59485
59687
  function checkStaleness({ lastUpdated, thresholdDays, now }) {
59486
59688
  if (!lastUpdated) {
@@ -59557,7 +59759,7 @@ function mapRawFinding(item) {
59557
59759
  function parseJson(raw) {
59558
59760
  return JSON.parse(raw);
59559
59761
  }
59560
- var safeJsonParse2 = (0, import_neverthrow23.fromThrowable)(parseJson);
59762
+ var safeJsonParse2 = (0, import_neverthrow24.fromThrowable)(parseJson);
59561
59763
  function parseClaudeResponse(raw) {
59562
59764
  const parseResult = safeJsonParse2(raw);
59563
59765
  if (parseResult.isErr()) {
@@ -59584,7 +59786,7 @@ async function downscaleBuffer(buffer) {
59584
59786
  return (0, import_sharp2.default)(buffer).resize({ width: targetWidth }).toBuffer();
59585
59787
  }
59586
59788
  function downscale(buffer) {
59587
- return (0, import_neverthrow24.fromAsyncThrowable)(
59789
+ return (0, import_neverthrow25.fromAsyncThrowable)(
59588
59790
  downscaleBuffer,
59589
59791
  (cause) => ({ type: "CLAUDE_API_FAILED", cause })
59590
59792
  )(buffer);
@@ -59657,7 +59859,7 @@ async function fetchClaudeText({
59657
59859
  return block?.type === "text" ? block.text : "";
59658
59860
  }
59659
59861
  function callClaude(options) {
59660
- return (0, import_neverthrow22.fromAsyncThrowable)(
59862
+ return (0, import_neverthrow23.fromAsyncThrowable)(
59661
59863
  fetchClaudeText,
59662
59864
  (cause) => ({ type: "CLAUDE_API_FAILED", cause })
59663
59865
  )(options);
@@ -59669,9 +59871,9 @@ function toFindings(text) {
59669
59871
  const cleaned = stripCodeFences(text);
59670
59872
  const findings = parseClaudeResponse(cleaned);
59671
59873
  if (!findings) {
59672
- return (0, import_neverthrow22.errAsync)({ type: "CLAUDE_RESPONSE_INVALID", raw: cleaned });
59874
+ return (0, import_neverthrow23.errAsync)({ type: "CLAUDE_RESPONSE_INVALID", raw: cleaned });
59673
59875
  }
59674
- return (0, import_neverthrow22.okAsync)(findings);
59876
+ return (0, import_neverthrow23.okAsync)(findings);
59675
59877
  }
59676
59878
  function buildResolveMessages(screenshotBase64, artboardNames) {
59677
59879
  return [
@@ -59708,12 +59910,12 @@ async function fetchResolveName({
59708
59910
  }
59709
59911
  function resolveArtboard(screenshot, artboardNames) {
59710
59912
  if (artboardNames.length === 0) {
59711
- return (0, import_neverthrow22.okAsync)(void 0);
59913
+ return (0, import_neverthrow23.okAsync)(void 0);
59712
59914
  }
59713
59915
  const anthropic = new Anthropic();
59714
59916
  return downscale(screenshot).andThen((scaled) => {
59715
59917
  const screenshotBase64 = scaled.toString("base64");
59716
- return (0, import_neverthrow22.fromAsyncThrowable)(
59918
+ return (0, import_neverthrow23.fromAsyncThrowable)(
59717
59919
  fetchResolveName,
59718
59920
  (cause) => ({ type: "CLAUDE_API_FAILED", cause })
59719
59921
  )({ anthropic, screenshotBase64, artboardNames }).map((name) => {
@@ -59776,7 +59978,7 @@ var SEVERITY_CONFIDENCE = {
59776
59978
  medium: CONFIDENCE_MEDIUM2,
59777
59979
  low: CONFIDENCE_LOW2
59778
59980
  };
59779
- var safeJsonParse22 = (0, import_neverthrow25.fromThrowable)(JSON.parse);
59981
+ var safeJsonParse22 = (0, import_neverthrow26.fromThrowable)(JSON.parse);
59780
59982
  function buildFindCandidatesMessages(screenshotBase64, artboardNames) {
59781
59983
  return [
59782
59984
  {
@@ -59823,12 +60025,12 @@ async function fetchCandidateNames({
59823
60025
  }
59824
60026
  function findCandidates(screenshot, artboardNames) {
59825
60027
  if (artboardNames.length === 0) {
59826
- return (0, import_neverthrow25.okAsync)([]);
60028
+ return (0, import_neverthrow26.okAsync)([]);
59827
60029
  }
59828
60030
  const anthropic = new Anthropic();
59829
60031
  return downscale(screenshot).andThen((scaled) => {
59830
60032
  const screenshotBase64 = scaled.toString("base64");
59831
- return (0, import_neverthrow25.fromAsyncThrowable)(
60033
+ return (0, import_neverthrow26.fromAsyncThrowable)(
59832
60034
  fetchCandidateNames,
59833
60035
  (cause) => ({ type: "CLAUDE_API_FAILED", cause })
59834
60036
  )({ anthropic, screenshotBase64, artboardNames });
@@ -59906,14 +60108,14 @@ async function fetchDesignContextText({
59906
60108
  function toDesignContextFindings(text) {
59907
60109
  const findings = parseConservativeResponse(text);
59908
60110
  if (!findings) {
59909
- return (0, import_neverthrow25.errAsync)({ type: "CLAUDE_RESPONSE_INVALID", raw: text });
60111
+ return (0, import_neverthrow26.errAsync)({ type: "CLAUDE_RESPONSE_INVALID", raw: text });
59910
60112
  }
59911
- return (0, import_neverthrow25.okAsync)(findings);
60113
+ return (0, import_neverthrow26.okAsync)(findings);
59912
60114
  }
59913
60115
  async function downscaleAll(buffers) {
59914
60116
  return Promise.all(buffers.map(async (buf) => downscaleBuffer(buf)));
59915
60117
  }
59916
- var downscaleAllBuffers = (0, import_neverthrow25.fromAsyncThrowable)(
60118
+ var downscaleAllBuffers = (0, import_neverthrow26.fromAsyncThrowable)(
59917
60119
  downscaleAll,
59918
60120
  (cause) => ({ type: "CLAUDE_API_FAILED", cause })
59919
60121
  );
@@ -59923,7 +60125,7 @@ function compareWithDesignContext(screenshot, artboards) {
59923
60125
  const screenshotBase64 = scaledScreenshot.toString("base64");
59924
60126
  return downscaleAllBuffers(artboards).andThen((scaledArtboards) => {
59925
60127
  const artboardBase64s = scaledArtboards.map((buf) => buf.toString("base64"));
59926
- return (0, import_neverthrow25.fromAsyncThrowable)(
60128
+ return (0, import_neverthrow26.fromAsyncThrowable)(
59927
60129
  fetchDesignContextText,
59928
60130
  (cause) => ({ type: "CLAUDE_API_FAILED", cause })
59929
60131
  )({ anthropic, screenshotBase64, artboardBase64s }).andThen(toDesignContextFindings);
@@ -60156,7 +60358,7 @@ async function initArtboardNames({ designStore, config: config3, state }) {
60156
60358
  return state.artboardNamesPromise;
60157
60359
  }
60158
60360
  function readScreenshot(screenshotPath, stepIndex) {
60159
- return import_neverthrow21.ResultAsync.fromThrowable(
60361
+ return import_neverthrow22.ResultAsync.fromThrowable(
60160
60362
  import_promises12.readFile,
60161
60363
  (cause) => ({ type: "SCREENSHOT_READ_FAILED", stepIndex, cause })
60162
60364
  )(screenshotPath);
@@ -60231,7 +60433,7 @@ function buildInspector(state, context) {
60231
60433
  const { promise: promise2, resolve } = Promise.withResolvers();
60232
60434
  state.resolve = resolve;
60233
60435
  applyTryResolve(state);
60234
- return (0, import_neverthrow20.fromSafePromise)(promise2);
60436
+ return (0, import_neverthrow21.fromSafePromise)(promise2);
60235
60437
  }
60236
60438
  };
60237
60439
  }
@@ -60255,10 +60457,10 @@ async function readAndParseSidecar(sidecarPath) {
60255
60457
  return parseMeta(raw);
60256
60458
  }
60257
60459
  function readSidecarFile(sidecarPath) {
60258
- return (0, import_neverthrow26.fromAsyncThrowable)(
60460
+ return (0, import_neverthrow27.fromAsyncThrowable)(
60259
60461
  readAndParseSidecar,
60260
60462
  () => ({})
60261
- )(sidecarPath).orElse(() => (0, import_neverthrow26.okAsync)({}));
60463
+ )(sidecarPath).orElse(() => (0, import_neverthrow27.okAsync)({}));
60262
60464
  }
60263
60465
  function isEnoent(error48) {
60264
60466
  return error48?.code === "ENOENT";
@@ -60267,13 +60469,13 @@ function wrapFsError(cause) {
60267
60469
  return { type: "FS_ERROR", cause };
60268
60470
  }
60269
60471
  function toFsError(fsError) {
60270
- return (0, import_neverthrow26.errAsync)(fsError);
60472
+ return (0, import_neverthrow27.errAsync)(fsError);
60271
60473
  }
60272
60474
  function missingBuffer() {
60273
- return (0, import_neverthrow26.okAsync)(void 0);
60475
+ return (0, import_neverthrow27.okAsync)(void 0);
60274
60476
  }
60275
60477
  function missingArtboard() {
60276
- return (0, import_neverthrow26.okAsync)(void 0);
60478
+ return (0, import_neverthrow27.okAsync)(void 0);
60277
60479
  }
60278
60480
  var FsDesignStore = class {
60279
60481
  designsDirectory;
@@ -60281,12 +60483,12 @@ var FsDesignStore = class {
60281
60483
  this.designsDirectory = designsDirectory;
60282
60484
  }
60283
60485
  listArtboards() {
60284
- return (0, import_neverthrow26.fromAsyncThrowable)(
60486
+ return (0, import_neverthrow27.fromAsyncThrowable)(
60285
60487
  import_promises13.readdir,
60286
60488
  wrapFsError
60287
60489
  )(this.designsDirectory).orElse((fsError) => {
60288
60490
  if (fsError.type === "FS_ERROR" && isEnoent(fsError.cause)) {
60289
- return (0, import_neverthrow26.okAsync)([]);
60491
+ return (0, import_neverthrow27.okAsync)([]);
60290
60492
  }
60291
60493
  return toFsError(fsError);
60292
60494
  }).map(
@@ -60296,7 +60498,7 @@ var FsDesignStore = class {
60296
60498
  getArtboard(filename) {
60297
60499
  const pngPath = import_node_path7.default.join(this.designsDirectory, `${filename}.png`);
60298
60500
  const sidecarPath = import_node_path7.default.join(this.designsDirectory, `${filename}.meta.yaml`);
60299
- return (0, import_neverthrow26.fromAsyncThrowable)(
60501
+ return (0, import_neverthrow27.fromAsyncThrowable)(
60300
60502
  import_promises13.readFile,
60301
60503
  wrapFsError
60302
60504
  )(pngPath).orElse((fsError) => {
@@ -60323,10 +60525,10 @@ function attemptRetry(options) {
60323
60525
  const { factory, config: config3, delayFunction, onRetry, attempt } = options;
60324
60526
  return factory().orElse((error48) => {
60325
60527
  if (attempt >= config3.maxAttempts) {
60326
- return (0, import_neverthrow29.errAsync)(error48);
60528
+ return (0, import_neverthrow30.errAsync)(error48);
60327
60529
  }
60328
60530
  const delay = config3.baseDelayMs * Math.pow(2, attempt - 1);
60329
- return import_neverthrow29.ResultAsync.fromPromise(
60531
+ return import_neverthrow30.ResultAsync.fromPromise(
60330
60532
  (onRetry?.({ attempt, maxAttempts: config3.maxAttempts, delayMs: delay, error: error48 }), delayFunction(delay)),
60331
60533
  () => error48
60332
60534
  ).andThen(
@@ -60346,7 +60548,7 @@ function withRetry(factory, options) {
60346
60548
  var CONSOLIDATOR_AGENT = "consolidator";
60347
60549
  function analyserFallback(artifacts, onEvent) {
60348
60550
  onEvent?.({ type: "AGENT_FAILED_NON_CRITICAL", agent: "analyser", attempts: RETRY_MAX_ATTEMPTS });
60349
- return (0, import_neverthrow28.okAsync)(artifacts.findings);
60551
+ return (0, import_neverthrow29.okAsync)(artifacts.findings);
60350
60552
  }
60351
60553
  function runAnalyserWithRetry(params) {
60352
60554
  const { artifacts, config: config3, onEvent } = params;
@@ -60376,7 +60578,7 @@ function resolveVisualFindings({
60376
60578
  onEvent
60377
60579
  }) {
60378
60580
  if (config3.analyser === void 0 || config3.signal?.aborted) {
60379
- return (0, import_neverthrow28.okAsync)(artifacts.findings);
60581
+ return (0, import_neverthrow29.okAsync)(artifacts.findings);
60380
60582
  }
60381
60583
  return runAnalyserWithRetry({ artifacts, config: config3, onEvent });
60382
60584
  }
@@ -60386,7 +60588,7 @@ function unmergedFallback(allFindings, onEvent) {
60386
60588
  agent: CONSOLIDATOR_AGENT,
60387
60589
  message: "Consolidation failed, returning unmerged findings"
60388
60590
  });
60389
- return (0, import_neverthrow28.okAsync)({ findings: allFindings, dismissed: [] });
60591
+ return (0, import_neverthrow29.okAsync)({ findings: allFindings, dismissed: [] });
60390
60592
  }
60391
60593
  function mergeWithFallback(options) {
60392
60594
  const {
@@ -60413,7 +60615,7 @@ function consolidate(options) {
60413
60615
  const { artifacts, inspectorFindings, runId, dismissals, config: config3, consolidatorConfig, onEvent } = options;
60414
60616
  return resolveVisualFindings({ artifacts, config: config3, onEvent }).andThen((visualFindings) => {
60415
60617
  if (config3.signal?.aborted) {
60416
- return (0, import_neverthrow28.okAsync)({ findings: artifacts.findings, dismissed: [] });
60618
+ return (0, import_neverthrow29.okAsync)({ findings: artifacts.findings, dismissed: [] });
60417
60619
  }
60418
60620
  return mergeWithFallback({
60419
60621
  artifacts,
@@ -60426,8 +60628,8 @@ function consolidate(options) {
60426
60628
  });
60427
60629
  });
60428
60630
  }
60429
- var safeReadFile = (0, import_neverthrow30.fromThrowable)((filePath) => (0, import_node_fs3.readFileSync)(filePath, "utf8"));
60430
- var safeParseJson = (0, import_neverthrow30.fromThrowable)(JSON.parse);
60631
+ var safeReadFile = (0, import_neverthrow31.fromThrowable)((filePath) => (0, import_node_fs3.readFileSync)(filePath, "utf8"));
60632
+ var safeParseJson = (0, import_neverthrow31.fromThrowable)(JSON.parse);
60431
60633
  function isEnoent2(error48) {
60432
60634
  if (!(error48 instanceof Error)) {
60433
60635
  return false;
@@ -60439,19 +60641,19 @@ function loadDismissals(filePath) {
60439
60641
  const readResult = safeReadFile(filePath);
60440
60642
  if (readResult.isErr()) {
60441
60643
  if (isEnoent2(readResult.error)) {
60442
- return (0, import_neverthrow30.ok)([]);
60644
+ return (0, import_neverthrow31.ok)([]);
60443
60645
  }
60444
- return (0, import_neverthrow30.err)({ type: "DISMISSALS_LOAD_FAILED", cause: readResult.error });
60646
+ return (0, import_neverthrow31.err)({ type: "DISMISSALS_LOAD_FAILED", cause: readResult.error });
60445
60647
  }
60446
60648
  return safeParseJson(readResult.value).mapErr((cause) => ({ type: "DISMISSALS_LOAD_FAILED", cause })).andThen((data) => {
60447
60649
  const store = data;
60448
60650
  if (!Array.isArray(store.dismissed)) {
60449
- return (0, import_neverthrow30.err)({
60651
+ return (0, import_neverthrow31.err)({
60450
60652
  type: "DISMISSALS_LOAD_FAILED",
60451
60653
  cause: "invalid shape: dismissed is not an array"
60452
60654
  });
60453
60655
  }
60454
- return (0, import_neverthrow30.ok)(store.dismissed);
60656
+ return (0, import_neverthrow31.ok)(store.dismissed);
60455
60657
  });
60456
60658
  }
60457
60659
  function toInspectorStepEvent(event) {
@@ -60521,7 +60723,7 @@ function runExplorerWithTeardown(explorerConfig, udid) {
60521
60723
  return runExplorer(explorerConfig).mapErr((cause) => ({ type: "EXPLORER_FAILED", cause })).andThen(
60522
60724
  (artifacts) => disableTouchIndicators(udid).mapErr(toSimulatorError).map(() => artifacts)
60523
60725
  ).orElse(
60524
- (error48) => disableTouchIndicators(udid).mapErr(toSimulatorError).andThen(() => (0, import_neverthrow31.errAsync)(error48)).orElse(() => (0, import_neverthrow31.errAsync)(error48))
60726
+ (error48) => disableTouchIndicators(udid).mapErr(toSimulatorError).andThen(() => (0, import_neverthrow32.errAsync)(error48)).orElse(() => (0, import_neverthrow32.errAsync)(error48))
60525
60727
  );
60526
60728
  }
60527
60729
  function runExplorerWithRetry(options) {
@@ -60547,12 +60749,12 @@ async function drainAfterExplorer(options) {
60547
60749
  inspector?.close();
60548
60750
  const inspectorFindings = inspector ? await collectInspectorFindings({ inspector, onEvent, totalSteps: enqueuedCount.value }) : [];
60549
60751
  if (explorerResult.isErr()) {
60550
- return (0, import_neverthrow31.err)(explorerResult.error);
60752
+ return (0, import_neverthrow32.err)(explorerResult.error);
60551
60753
  }
60552
- return (0, import_neverthrow31.ok)({ artifacts: explorerResult.value, inspectorFindings });
60754
+ return (0, import_neverthrow32.ok)({ artifacts: explorerResult.value, inspectorFindings });
60553
60755
  }
60554
60756
  function runExplorerAndDrain(options) {
60555
- return new import_neverthrow31.ResultAsync(drainAfterExplorer(options));
60757
+ return new import_neverthrow32.ResultAsync(drainAfterExplorer(options));
60556
60758
  }
60557
60759
  var require2 = (0, import_node_module.createRequire)(__importMetaUrl);
60558
60760
  function createMobileMcpServer() {
@@ -60570,7 +60772,7 @@ function runAnalysis(artifacts, config3) {
60570
60772
  }
60571
60773
  var ISO_DATE_LENGTH2 = 10;
60572
60774
  var RUN_ID_PAD_LENGTH = 4;
60573
- var safeReaddirSync = (0, import_neverthrow27.fromThrowable)((directory) => (0, import_node_fs2.readdirSync)(directory));
60775
+ var safeReaddirSync = (0, import_neverthrow28.fromThrowable)((directory) => (0, import_node_fs2.readdirSync)(directory));
60574
60776
  function nextRunId(outputDirectory, date5) {
60575
60777
  const entries = safeReaddirSync(`${outputDirectory}/${date5}`).unwrapOr([]);
60576
60778
  let max = 0;
@@ -60582,7 +60784,7 @@ function nextRunId(outputDirectory, date5) {
60582
60784
  }
60583
60785
  return String(max + 1).padStart(RUN_ID_PAD_LENGTH, "0");
60584
60786
  }
60585
- var writeOutputFile = (0, import_neverthrow27.fromThrowable)(
60787
+ var writeOutputFile = (0, import_neverthrow28.fromThrowable)(
60586
60788
  (params) => {
60587
60789
  const { findingsPath, outputDirectory, json: json3 } = params;
60588
60790
  (0, import_node_fs2.mkdirSync)(outputDirectory, { recursive: true });
@@ -60595,15 +60797,15 @@ function validatePipelineConfig(config3) {
60595
60797
  const runId = config3.runId ?? nextRunId(config3.outputDir, date5);
60596
60798
  const runPathsResult = resolveRunPaths({ outputDirectory: config3.outputDir, runId, date: date5 });
60597
60799
  if (runPathsResult.isErr()) {
60598
- return (0, import_neverthrow27.err)({ type: "RUN_PATHS_FAILED", cause: runPathsResult.error });
60800
+ return (0, import_neverthrow28.err)({ type: "RUN_PATHS_FAILED", cause: runPathsResult.error });
60599
60801
  }
60600
60802
  const dismissalsResult = loadDismissals(
60601
60803
  dismissalsPath(config3.outputDir, process.env.QA_DISMISSALS_PATH)
60602
60804
  );
60603
60805
  if (dismissalsResult.isErr()) {
60604
- return (0, import_neverthrow27.err)(dismissalsResult.error);
60806
+ return (0, import_neverthrow28.err)(dismissalsResult.error);
60605
60807
  }
60606
- return (0, import_neverthrow27.ok)({ runId, date: date5, runPaths: runPathsResult.value, dismissals: dismissalsResult.value });
60808
+ return (0, import_neverthrow28.ok)({ runId, date: date5, runPaths: runPathsResult.value, dismissals: dismissalsResult.value });
60607
60809
  }
60608
60810
  function buildExplorerConfig({
60609
60811
  config: config3,
@@ -60638,11 +60840,11 @@ function buildOutput(consolidationResult, options) {
60638
60840
  function buildPipelineSetup(config3) {
60639
60841
  const validatedResult = validatePipelineConfig(config3);
60640
60842
  if (validatedResult.isErr()) {
60641
- return (0, import_neverthrow27.err)(validatedResult.error);
60843
+ return (0, import_neverthrow28.err)(validatedResult.error);
60642
60844
  }
60643
60845
  const { runId, date: date5, runPaths, dismissals } = validatedResult.value;
60644
60846
  const { inspector, explorerOnEvent, enqueuedCount } = buildInspectorSetup(config3);
60645
- return (0, import_neverthrow27.ok)({
60847
+ return (0, import_neverthrow28.ok)({
60646
60848
  runId,
60647
60849
  udid: config3.simulatorUdid ?? "booted",
60648
60850
  runPaths,
@@ -60686,19 +60888,19 @@ function executePipeline(setup, config3) {
60686
60888
  function runPipeline2(config3) {
60687
60889
  const setupResult = buildPipelineSetup(config3);
60688
60890
  if (setupResult.isErr()) {
60689
- return (0, import_neverthrow27.errAsync)(setupResult.error);
60891
+ return (0, import_neverthrow28.errAsync)(setupResult.error);
60690
60892
  }
60691
60893
  return executePipeline(setupResult.value, config3);
60692
60894
  }
60693
60895
 
60694
60896
  // src/commands/analyse-command.ts
60695
- var import_neverthrow32 = __toESM(require_index_cjs(), 1);
60897
+ var import_neverthrow33 = __toESM(require_index_cjs(), 1);
60696
60898
  var JSON_INDENT = 2;
60697
60899
  function buildArtifacts(videoPath) {
60698
60900
  return { videoPath, videoPath2x: "", videoPath4x: videoPath, findings: [], snapshots: [] };
60699
60901
  }
60700
60902
  async function checkVideoPathExists(videoPath) {
60701
- const safeAccess = (0, import_neverthrow32.fromAsyncThrowable)(import_promises16.access, () => ({ type: "FILE_NOT_FOUND" }));
60903
+ const safeAccess = (0, import_neverthrow33.fromAsyncThrowable)(import_promises16.access, () => ({ type: "FILE_NOT_FOUND" }));
60702
60904
  const result = await safeAccess(videoPath);
60703
60905
  return result.isOk();
60704
60906
  }
@@ -60742,7 +60944,7 @@ async function runAnalyseCommand(videoPath, config3) {
60742
60944
  }
60743
60945
 
60744
60946
  // src/core/completion-generator.ts
60745
- var import_neverthrow33 = __toESM(require_index_cjs(), 1);
60947
+ var import_neverthrow34 = __toESM(require_index_cjs(), 1);
60746
60948
  function extractLongFlags(flags) {
60747
60949
  return flags.split(/[\s,]+/).filter((token) => token.startsWith("--"));
60748
60950
  }
@@ -60832,9 +61034,9 @@ complete -F _xqa_completion xqa`;
60832
61034
  }
60833
61035
  function generateCompletion(commands, shell) {
60834
61036
  if (shell !== "bash" && shell !== "zsh") {
60835
- return (0, import_neverthrow33.err)({ type: "UNSUPPORTED_SHELL", shell });
61037
+ return (0, import_neverthrow34.err)({ type: "UNSUPPORTED_SHELL", shell });
60836
61038
  }
60837
- return (0, import_neverthrow33.ok)(shell === "zsh" ? generateZshCompletion(commands) : generateBashCompletion(commands));
61039
+ return (0, import_neverthrow34.ok)(shell === "zsh" ? generateZshCompletion(commands) : generateBashCompletion(commands));
60838
61040
  }
60839
61041
 
60840
61042
  // src/commands/completion-command.ts
@@ -60893,16 +61095,16 @@ var DEFAULT_ABORT_EXIT_CODE = 130;
60893
61095
  // src/core/last-path.ts
60894
61096
  var import_node_fs4 = require("node:fs");
60895
61097
  var import_node_path8 = __toESM(require("node:path"), 1);
60896
- var import_neverthrow34 = __toESM(require_index_cjs(), 1);
61098
+ var import_neverthrow35 = __toESM(require_index_cjs(), 1);
60897
61099
  function resolveLastPath(argument, stateContent) {
60898
61100
  if (argument !== void 0) {
60899
- return (0, import_neverthrow34.ok)(argument);
61101
+ return (0, import_neverthrow35.ok)(argument);
60900
61102
  }
60901
61103
  const trimmed = stateContent?.trim();
60902
61104
  if (trimmed) {
60903
- return (0, import_neverthrow34.ok)(trimmed);
61105
+ return (0, import_neverthrow35.ok)(trimmed);
60904
61106
  }
60905
- return (0, import_neverthrow34.err)({ type: "NO_ARG_AND_NO_STATE" });
61107
+ return (0, import_neverthrow35.err)({ type: "NO_ARG_AND_NO_STATE" });
60906
61108
  }
60907
61109
  function lastPathFilePath(xqaDirectoryectory) {
60908
61110
  return import_node_path8.default.join(xqaDirectoryectory, "last-findings-path");
@@ -60911,53 +61113,61 @@ function writeLastPath(xqaDirectory, findingsPath) {
60911
61113
  (0, import_node_fs4.writeFileSync)(lastPathFilePath(xqaDirectory), findingsPath);
60912
61114
  }
60913
61115
 
60914
- // src/shell/instructions.ts
61116
+ // src/shell/app-context.ts
60915
61117
  var import_promises17 = require("node:fs/promises");
60916
61118
  var import_node_path9 = __toESM(require("node:path"), 1);
60917
- var import_neverthrow35 = __toESM(require_index_cjs(), 1);
61119
+ var import_neverthrow36 = __toESM(require_index_cjs(), 1);
60918
61120
  var HTML_COMMENT_PATTERN = /<!--[\s\S]*?-->/g;
60919
61121
  function isEnoentError(value) {
60920
61122
  return value !== null && typeof value === "object" && "code" in value && value.code === "ENOENT";
60921
61123
  }
60922
- function toInstructionsError(cause) {
61124
+ function toAppContextError(cause) {
60923
61125
  return { type: "READ_FAILED", cause };
60924
61126
  }
60925
- function absentInstructions() {
61127
+ function absentContext() {
60926
61128
  const absent = void 0;
60927
- return (0, import_neverthrow35.ok)(absent);
61129
+ return (0, import_neverthrow36.ok)(absent);
60928
61130
  }
60929
- var safeReadFile2 = import_neverthrow35.ResultAsync.fromThrowable(
61131
+ var safeReadFile2 = import_neverthrow36.ResultAsync.fromThrowable(
60930
61132
  async (filePath) => (0, import_promises17.readFile)(filePath, "utf8"),
60931
- toInstructionsError
61133
+ toAppContextError
60932
61134
  );
60933
61135
  function stripAndNormalize(content) {
60934
61136
  const stripped = content.replaceAll(HTML_COMMENT_PATTERN, "").trim();
60935
61137
  return stripped.length === 0 ? void 0 : stripped;
60936
61138
  }
60937
- function readInstructions(xqaDirectory) {
60938
- const filePath = import_node_path9.default.join(xqaDirectory, "instructions.md");
61139
+ function readContextFile(xqaDirectory, filename) {
61140
+ const filePath = import_node_path9.default.join(xqaDirectory, filename);
60939
61141
  return safeReadFile2(filePath).map((content) => stripAndNormalize(content)).orElse((error48) => {
60940
61142
  if (isEnoentError(error48.cause)) {
60941
- return absentInstructions();
61143
+ return absentContext();
60942
61144
  }
60943
- return (0, import_neverthrow35.err)(error48);
61145
+ return (0, import_neverthrow36.err)(error48);
60944
61146
  });
60945
61147
  }
61148
+ function readAppContext(xqaDirectory) {
61149
+ return readContextFile(xqaDirectory, "app.md");
61150
+ }
61151
+ function readExploreContext(xqaDirectory) {
61152
+ return readContextFile(xqaDirectory, "explore.md");
61153
+ }
60946
61154
 
60947
61155
  // src/commands/explore-command.ts
60948
61156
  function buildExplorerConfig2({
60949
61157
  input,
60950
61158
  config: config3,
60951
- instructions
61159
+ appContext,
61160
+ initialState
60952
61161
  }) {
60953
- const parts = [instructions, input.prompt].filter(Boolean);
60954
- const userPrompt = parts.length > 0 ? parts.join("\n\n") : void 0;
61162
+ const parts = [initialState, input.prompt].filter(Boolean);
61163
+ const resolvedStartingState = parts.length > 0 ? parts.join("\n\n") : void 0;
60955
61164
  return {
60956
61165
  mode: "freestyle",
60957
61166
  mcpServers: createDefaultMcpServers(),
60958
61167
  allowedTools: ALLOWED_TOOLS,
60959
61168
  timeoutMs: config3.QA_EXPLORE_TIMEOUT_SECONDS === void 0 ? void 0 : config3.QA_EXPLORE_TIMEOUT_SECONDS * MS_PER_SECOND3,
60960
- userPrompt,
61169
+ appContext,
61170
+ initialState: resolvedStartingState,
60961
61171
  buildEnv: config3.QA_BUILD_ENV
60962
61172
  };
60963
61173
  }
@@ -60970,7 +61180,7 @@ function buildPipelineConfig({
60970
61180
  const base = {
60971
61181
  outputDir: import_node_path10.default.join(xqaDirectory, "output"),
60972
61182
  runId: config3.QA_RUN_ID,
60973
- onEvent: createConsoleObserver(input.verbose ? { verbose: true } : void 0),
61183
+ onEvent: createConsoleObserver(input.verbose ? { verbose: input.verbose } : void 0),
60974
61184
  signal: input.signal,
60975
61185
  inspector: { designsDirectory: import_node_path10.default.join(xqaDirectory, "designs") },
60976
61186
  explorer
@@ -61008,69 +61218,114 @@ ${cause}
61008
61218
  }
61009
61219
  };
61010
61220
  }
61221
+ function handleContextError(error48) {
61222
+ const cause = error48.cause instanceof Error ? error48.cause.message : JSON.stringify(error48.cause);
61223
+ process.stderr.write(`Failed to read context: ${error48.type}
61224
+ ${cause}
61225
+ `);
61226
+ process.exit(1);
61227
+ }
61011
61228
  function runExploreCommand(input, options) {
61012
61229
  const { config: config3, xqaDirectory } = options;
61013
61230
  const { onSuccess, onError } = handlePipelineResult(input, xqaDirectory);
61014
- void readInstructions(xqaDirectory).match(
61015
- (instructions) => {
61016
- const explorerConfig = buildExplorerConfig2({ input, config: config3, instructions });
61017
- void runPipeline2(
61018
- buildPipelineConfig({ input, config: config3, xqaDirectory, explorer: explorerConfig })
61019
- ).match(onSuccess, onError);
61020
- },
61021
- (error48) => {
61022
- const cause = error48.cause instanceof Error ? error48.cause.message : JSON.stringify(error48.cause);
61023
- process.stderr.write(`Failed to read instructions: ${error48.type}
61024
- ${cause}
61025
- `);
61026
- process.exit(1);
61027
- }
61028
- );
61231
+ void readAppContext(xqaDirectory).andThen(
61232
+ (appContext) => readExploreContext(xqaDirectory).map((exploreContext) => ({ appContext, exploreContext }))
61233
+ ).match(({ appContext, exploreContext }) => {
61234
+ const explorerConfig = buildExplorerConfig2({
61235
+ input,
61236
+ config: config3,
61237
+ appContext,
61238
+ initialState: exploreContext
61239
+ });
61240
+ void runPipeline2(
61241
+ buildPipelineConfig({ input, config: config3, xqaDirectory, explorer: explorerConfig })
61242
+ ).match(onSuccess, onError);
61243
+ }, handleContextError);
61029
61244
  }
61030
61245
 
61031
61246
  // src/commands/init-command.ts
61247
+ var import_node_child_process5 = require("node:child_process");
61032
61248
  var import_node_fs5 = require("node:fs");
61033
61249
  var import_node_path11 = __toESM(require("node:path"), 1);
61250
+ var import_node_url = require("node:url");
61034
61251
  var GITIGNORE_CONTENT = `/output
61035
61252
  /last-findings-path
61036
61253
  `;
61037
- var INSTRUCTIONS_TEMPLATE = `<!-- App Overview
61038
- Describe what your app does and its main purpose.
61039
- Example: This is a crypto wallet app that lets users send, receive, and swap tokens.
61254
+ var APP_TEMPLATE = `<!-- Overview
61255
+ What this app does in 1-2 sentences. Focus on domain, not tech stack.
61256
+ Example: Crypto wallet for sending, receiving, and swapping tokens across multiple blockchains.
61040
61257
  -->
61041
61258
 
61042
- <!-- Navigation
61043
- Describe the main navigation structure and how to move between screens.
61044
- Example: The main screen is the asset list. Swipe down to open Profile. Dismiss modals by swiping down.
61259
+ <!-- Screens
61260
+ List the main screens and how to reach them. Use > for navigation paths.
61261
+ Include any non-obvious names the accessibility tree uses for screen titles.
61262
+ Example:
61263
+ - Portfolio: default home screen, shows asset list
61264
+ - Asset Detail: tap any asset in Portfolio
61265
+ - Settings: tap the gear icon top-right on Portfolio
61266
+ - Send: Portfolio > tap asset > Send button
61267
+ If the accessibility tree uses a different name than what's visible, include both.
61045
61268
  -->
61046
61269
 
61047
- <!-- Startup
61048
- Describe the initial state of the app when the agent starts.
61049
- Example: The app starts on the home screen with a wallet already loaded.
61050
- If this file contains a mnemonic phrase, add .xqa/instructions.md to your .gitignore.
61270
+ <!-- Gestures
61271
+ Optional. List navigation gestures that have no visible button. The agent cannot discover these from the UI tree.
61272
+ Skip this section if your app does not use gesture-based navigation.
61273
+ Example:
61274
+ - Swipe down on Portfolio \u2192 opens Profile
61275
+ - Swipe down on any modal \u2192 dismisses it
61276
+ - Swipe left on Asset Detail \u2192 goes back (no back button visible)
61051
61277
  -->
61052
61278
  `;
61279
+ var EXPLORE_TEMPLATE = `<!-- Starting State
61280
+ Describe the exact screen and state the app is in when the agent connects.
61281
+ Include credentials or wallet state if relevant. Add explore.md to .gitignore if it contains secrets.
61282
+ Example: App is on the Portfolio screen with a funded wallet loaded. No modals are open.
61283
+ Example with credentials: App is on the Login screen. Use PIN 123456 to unlock.
61284
+ Example with mid-flow state: App is on the Send flow, amount entry modal is open. Dismiss before exploring.
61285
+ -->
61286
+
61287
+ <!-- Scope
61288
+ Optional. Tell the agent where to focus or what to skip.
61289
+ Without this, the agent explores everything reachable from the starting screen.
61290
+ Example: Focus on the Settings section only. Skip the Send and Receive flows.
61291
+ Example: Explore everything except the Swap screen \u2014 it requires live network.
61292
+ Scope applies from the starting screen. If the focus area requires navigation, describe that in Starting State instead.
61293
+ -->
61294
+ `;
61295
+ function resolveSkillPath(skillName) {
61296
+ const packageDistributionDirectory = import_node_path11.default.dirname((0, import_node_url.fileURLToPath)(__importMetaUrl));
61297
+ return import_node_path11.default.join(packageDistributionDirectory, "skills", skillName);
61298
+ }
61053
61299
  function runInitCommand() {
61054
61300
  const xqaDirectory = import_node_path11.default.join(process.cwd(), ".xqa");
61301
+ (0, import_node_child_process5.spawnSync)("npx", ["skills", "add", resolveSkillPath("xqa-spec")], {
61302
+ stdio: "inherit"
61303
+ });
61055
61304
  if ((0, import_node_fs5.existsSync)(xqaDirectory)) {
61056
- process.stderr.write(`xqa already initialized: ${xqaDirectory}
61305
+ process.stdout.write(`Skills updated. .xqa already exists, skipping project init.
61057
61306
  `);
61058
- process.exit(1);
61059
61307
  return;
61060
61308
  }
61061
61309
  (0, import_node_fs5.mkdirSync)(xqaDirectory);
61062
61310
  (0, import_node_fs5.writeFileSync)(import_node_path11.default.join(xqaDirectory, ".gitignore"), GITIGNORE_CONTENT);
61063
- (0, import_node_fs5.writeFileSync)(import_node_path11.default.join(xqaDirectory, "instructions.md"), INSTRUCTIONS_TEMPLATE);
61311
+ (0, import_node_fs5.writeFileSync)(import_node_path11.default.join(xqaDirectory, "app.md"), APP_TEMPLATE);
61312
+ (0, import_node_fs5.writeFileSync)(import_node_path11.default.join(xqaDirectory, "explore.md"), EXPLORE_TEMPLATE);
61313
+ for (const subdir of ["designs", "specs", "suites"]) {
61314
+ (0, import_node_fs5.mkdirSync)(import_node_path11.default.join(xqaDirectory, subdir));
61315
+ (0, import_node_fs5.writeFileSync)(import_node_path11.default.join(xqaDirectory, subdir, ".gitkeep"), "");
61316
+ }
61064
61317
  process.stdout.write(`Initialized xqa project: ${xqaDirectory}
61065
61318
  `);
61066
- process.stdout.write(`Edit .xqa/instructions.md to describe your app.
61067
- `);
61319
+ process.stdout.write(
61320
+ `Edit .xqa/app.md to describe your app and .xqa/explore.md to configure exploration.
61321
+ `
61322
+ );
61068
61323
  }
61069
61324
 
61070
61325
  // src/commands/review-command.ts
61071
61326
  var import_node_fs6 = require("node:fs");
61072
61327
  var import_node_path13 = __toESM(require("node:path"), 1);
61073
- var import_neverthrow37 = __toESM(require_index_cjs(), 1);
61328
+ var import_neverthrow38 = __toESM(require_index_cjs(), 1);
61074
61329
 
61075
61330
  // ../../node_modules/.pnpm/@inquirer+core@10.3.2_@types+node@22.19.15/node_modules/@inquirer/core/dist/esm/lib/key.js
61076
61331
  var isUpKey = (key, keybindings = []) => (
@@ -63475,7 +63730,7 @@ var esm_default11 = createPrompt((config3, done) => {
63475
63730
  });
63476
63731
 
63477
63732
  // src/review-session.ts
63478
- var import_neverthrow36 = __toESM(require_index_cjs(), 1);
63733
+ var import_neverthrow37 = __toESM(require_index_cjs(), 1);
63479
63734
  var CONFIDENCE_PERCENT = 100;
63480
63735
  var FLOW_COL_WIDTH = 35;
63481
63736
  var TRIGGER_COL_WIDTH = 16;
@@ -63628,15 +63883,15 @@ async function runInteractiveLoop(findings, existing) {
63628
63883
  }
63629
63884
  return { staged: state.staged, undoneKeys: state.undoneKeys };
63630
63885
  }
63631
- var safeRunInteractiveLoop = (0, import_neverthrow36.fromAsyncThrowable)(
63886
+ var safeRunInteractiveLoop = (0, import_neverthrow37.fromAsyncThrowable)(
63632
63887
  runInteractiveLoop,
63633
63888
  (error48) => error48 instanceof Error && error48.name === "ExitPromptError" ? "exit-prompt" : "unexpected"
63634
63889
  );
63635
63890
 
63636
63891
  // src/commands/review-command.ts
63637
- var safeReadFile3 = (0, import_neverthrow37.fromThrowable)((filePath) => (0, import_node_fs6.readFileSync)(filePath, "utf8"));
63638
- var safeParseJson2 = (0, import_neverthrow37.fromThrowable)(JSON.parse);
63639
- var safeWrite = (0, import_neverthrow37.fromThrowable)((filePath, content) => {
63892
+ var safeReadFile3 = (0, import_neverthrow38.fromThrowable)((filePath) => (0, import_node_fs6.readFileSync)(filePath, "utf8"));
63893
+ var safeParseJson2 = (0, import_neverthrow38.fromThrowable)(JSON.parse);
63894
+ var safeWrite = (0, import_neverthrow38.fromThrowable)((filePath, content) => {
63640
63895
  (0, import_node_fs6.writeFileSync)(filePath, content);
63641
63896
  });
63642
63897
  function readLastPath(xqaDirectory) {
@@ -63653,13 +63908,13 @@ function isPipelineOutput(data) {
63653
63908
  function readFindings(filePath) {
63654
63909
  const readResult = safeReadFile3(filePath);
63655
63910
  if (readResult.isErr()) {
63656
- return (0, import_neverthrow37.err)("not-found");
63911
+ return (0, import_neverthrow38.err)("not-found");
63657
63912
  }
63658
63913
  return safeParseJson2(readResult.value).mapErr(() => "invalid").andThen((data) => {
63659
63914
  if (!isPipelineOutput(data)) {
63660
- return (0, import_neverthrow37.err)("invalid");
63915
+ return (0, import_neverthrow38.err)("invalid");
63661
63916
  }
63662
- return (0, import_neverthrow37.ok)(data);
63917
+ return (0, import_neverthrow38.ok)(data);
63663
63918
  });
63664
63919
  }
63665
63920
  function loadExistingDismissals(filePath) {
@@ -63732,7 +63987,7 @@ function resolveAndReadFindings(findingsPath, xqaDirectory) {
63732
63987
  "No findings path provided and no last path found. Run: xqa review <findings-path>\n"
63733
63988
  );
63734
63989
  process.exit(1);
63735
- return (0, import_neverthrow37.err)();
63990
+ return (0, import_neverthrow38.err)();
63736
63991
  }
63737
63992
  const resolvedPath = resolvedPathResult.value;
63738
63993
  const findingsResult = readFindings(resolvedPath);
@@ -63745,9 +64000,9 @@ function resolveAndReadFindings(findingsPath, xqaDirectory) {
63745
64000
  `);
63746
64001
  }
63747
64002
  process.exit(1);
63748
- return (0, import_neverthrow37.err)();
64003
+ return (0, import_neverthrow38.err)();
63749
64004
  }
63750
- return (0, import_neverthrow37.ok)({ resolvedPath, output: findingsResult.value });
64005
+ return (0, import_neverthrow38.ok)({ resolvedPath, output: findingsResult.value });
63751
64006
  }
63752
64007
  async function runReviewLoop({
63753
64008
  findings,
@@ -63800,46 +64055,43 @@ async function runReviewCommand(findingsPath, xqaDirectory) {
63800
64055
  // src/commands/spec-command.ts
63801
64056
  var import_node_fs7 = require("node:fs");
63802
64057
  var import_node_path15 = __toESM(require("node:path"), 1);
63803
- var import_neverthrow39 = __toESM(require_index_cjs(), 1);
64058
+ var import_neverthrow40 = __toESM(require_index_cjs(), 1);
63804
64059
 
63805
64060
  // src/spec-frontmatter.ts
63806
- var import_neverthrow38 = __toESM(require_index_cjs(), 1);
64061
+ var import_neverthrow39 = __toESM(require_index_cjs(), 1);
63807
64062
  var FRONTMATTER_OPEN_LEN = 4;
63808
64063
  var FRONTMATTER_MARKER_LEN = 3;
63809
64064
  function extractFrontmatterBlock(content) {
63810
64065
  const normalized = content.replaceAll("\r\n", "\n");
63811
64066
  if (!normalized.startsWith("---")) {
63812
- return (0, import_neverthrow38.err)({ type: "MISSING_FRONTMATTER" });
64067
+ return (0, import_neverthrow39.err)({ type: "MISSING_FRONTMATTER" });
63813
64068
  }
63814
64069
  const end = normalized.indexOf("\n---", FRONTMATTER_MARKER_LEN);
63815
64070
  if (end === -1) {
63816
- return (0, import_neverthrow38.err)({ type: "MISSING_FRONTMATTER" });
64071
+ return (0, import_neverthrow39.err)({ type: "MISSING_FRONTMATTER" });
63817
64072
  }
63818
- return (0, import_neverthrow38.ok)(normalized.slice(FRONTMATTER_OPEN_LEN, end));
64073
+ return (0, import_neverthrow39.ok)(normalized.slice(FRONTMATTER_OPEN_LEN, end));
63819
64074
  }
63820
- function parseMaxSteps(fields) {
63821
- const maxStepsRaw = fields.get("max_steps");
63822
- if (maxStepsRaw === void 0) {
63823
- return (0, import_neverthrow38.ok)(maxStepsRaw);
64075
+ function parseTimeout(fields) {
64076
+ const raw = fields.get("timeout");
64077
+ if (raw === void 0) {
64078
+ return (0, import_neverthrow39.ok)(raw);
63824
64079
  }
63825
- const parsed = Number(maxStepsRaw);
63826
- if (!Number.isInteger(parsed) || parsed <= 0) {
63827
- return (0, import_neverthrow38.err)({ type: "PARSE_ERROR", cause: `invalid max_steps: ${maxStepsRaw}` });
64080
+ const parsed = Number(raw);
64081
+ if (Number.isNaN(parsed) || parsed <= 0) {
64082
+ return (0, import_neverthrow39.err)({ type: "PARSE_ERROR", cause: `invalid timeout: ${raw}` });
63828
64083
  }
63829
- return (0, import_neverthrow38.ok)(parsed);
64084
+ return (0, import_neverthrow39.ok)(parsed);
63830
64085
  }
63831
64086
  function parseSpecFrontmatter(content) {
63832
64087
  return extractFrontmatterBlock(content).andThen((block) => {
63833
64088
  const fields = parseYamlFields(block);
63834
64089
  const feature = fields.get("feature");
63835
64090
  if (feature === void 0) {
63836
- return (0, import_neverthrow38.err)({ type: "MISSING_FIELD", field: "feature" });
64091
+ return (0, import_neverthrow39.err)({ type: "MISSING_FIELD", field: "feature" });
63837
64092
  }
63838
64093
  const entry = fields.get("entry");
63839
- if (entry === void 0) {
63840
- return (0, import_neverthrow38.err)({ type: "MISSING_FIELD", field: "entry" });
63841
- }
63842
- return parseMaxSteps(fields).map((maxSteps) => ({ feature, entry, maxSteps }));
64094
+ return parseTimeout(fields).map((timeout) => ({ feature, entry, timeout }));
63843
64095
  });
63844
64096
  }
63845
64097
  function parseYamlFields(block) {
@@ -63877,14 +64129,24 @@ function stripExtensions(filename) {
63877
64129
  }
63878
64130
 
63879
64131
  // src/commands/spec-command.ts
63880
- var safeReadFile4 = (0, import_neverthrow39.fromThrowable)((filePath) => (0, import_node_fs7.readFileSync)(filePath, "utf8"));
64132
+ var safeReadFile4 = (0, import_neverthrow40.fromThrowable)((filePath) => (0, import_node_fs7.readFileSync)(filePath, "utf8"));
64133
+ var safeReaddir = (0, import_neverthrow40.fromThrowable)(
64134
+ (directory) => (0, import_node_fs7.readdirSync)(directory, { recursive: true, encoding: "utf8" })
64135
+ );
64136
+ var CANCEL = "xqa:cancel";
64137
+ var safeSelect = import_neverthrow40.ResultAsync.fromThrowable(
64138
+ esm_default11,
64139
+ (error48) => error48 instanceof Error && error48.name === "ExitPromptError" ? "cancelled" : "failed"
64140
+ );
63881
64141
  function buildSpecExplorer(input, context) {
63882
64142
  return {
63883
64143
  mode: "spec",
63884
64144
  specFiles: [context.absolutePath],
63885
64145
  mcpServers: createDefaultMcpServers(),
63886
64146
  allowedTools: ALLOWED_TOOLS,
63887
- userPrompt: `Navigate to \`${context.entry}\` before beginning spec verification.`,
64147
+ timeoutMs: context.timeout === void 0 ? void 0 : context.timeout * MS_PER_SECOND3,
64148
+ appContext: context.appContext,
64149
+ initialState: context.entry ? `Navigate to \`${context.entry}\` before beginning spec verification.` : void 0,
63888
64150
  buildEnv: context.config.QA_BUILD_ENV
63889
64151
  };
63890
64152
  }
@@ -63892,7 +64154,7 @@ function buildPipelineConfig2(input, context) {
63892
64154
  return {
63893
64155
  outputDir: import_node_path15.default.join(context.xqaDirectory, "output", context.slug),
63894
64156
  signal: input.signal,
63895
- onEvent: createConsoleObserver(input.verbose ? { verbose: true } : void 0),
64157
+ onEvent: createConsoleObserver(input.verbose ? { verbose: input.verbose } : void 0),
63896
64158
  inspector: { designsDirectory: import_node_path15.default.join(context.xqaDirectory, "designs") },
63897
64159
  explorer: buildSpecExplorer(input, context)
63898
64160
  };
@@ -63937,33 +64199,95 @@ ${cause}
63937
64199
  `);
63938
64200
  process.exit(1);
63939
64201
  }
64202
+ function findSpecFiles(xqaDirectory) {
64203
+ const specsDirectory = import_node_path15.default.join(xqaDirectory, "specs");
64204
+ return safeReaddir(specsDirectory).unwrapOr([]).filter((file2) => file2.endsWith(".test.md")).map((file2) => import_node_path15.default.join(specsDirectory, file2));
64205
+ }
64206
+ async function promptForSpec(specFiles, xqaDirectory) {
64207
+ const result = await safeSelect({
64208
+ message: "Select a spec",
64209
+ choices: [
64210
+ ...specFiles.map((specFile) => ({
64211
+ name: import_node_path15.default.relative(xqaDirectory, specFile),
64212
+ value: specFile
64213
+ })),
64214
+ new Separator(),
64215
+ { name: "Cancel", value: CANCEL }
64216
+ ]
64217
+ });
64218
+ if (result.isErr() || result.value === CANCEL) {
64219
+ process.exit(0);
64220
+ return void 0;
64221
+ }
64222
+ return result.value;
64223
+ }
64224
+ async function resolveSpecFile(specFile, xqaDirectory) {
64225
+ if (specFile !== void 0) {
64226
+ return specFile;
64227
+ }
64228
+ const specFiles = findSpecFiles(xqaDirectory);
64229
+ if (specFiles.length === 0) {
64230
+ process.stderr.write("No spec files found in .xqa/specs/. Create one with /xqa-spec.\n");
64231
+ process.exit(1);
64232
+ return void 0;
64233
+ }
64234
+ return promptForSpec(specFiles, xqaDirectory);
64235
+ }
64236
+ async function executeSpec(input, context) {
64237
+ const result = await runPipeline2(buildPipelineConfig2(input, context));
64238
+ result.match((output) => {
64239
+ handleSpecSuccess(context.xqaDirectory, output);
64240
+ }, handleSpecError);
64241
+ }
64242
+ function handleAppContextError(error48) {
64243
+ const cause = error48.cause instanceof Error ? error48.cause.message : JSON.stringify(error48.cause);
64244
+ process.stderr.write(`Failed to read app context: ${error48.type}
64245
+ ${cause}
64246
+ `);
64247
+ process.exit(1);
64248
+ }
64249
+ async function buildContext(options, specData) {
64250
+ const appContextResult = await readAppContext(options.xqaDirectory);
64251
+ if (appContextResult.isErr()) {
64252
+ handleAppContextError(appContextResult.error);
64253
+ return void 0;
64254
+ }
64255
+ return {
64256
+ config: options.config,
64257
+ xqaDirectory: options.xqaDirectory,
64258
+ absolutePath: specData.absolutePath,
64259
+ entry: specData.entry,
64260
+ timeout: specData.timeout,
64261
+ slug: deriveSpecSlug(specData.absolutePath),
64262
+ appContext: appContextResult.value
64263
+ };
64264
+ }
63940
64265
  async function runSpecCommand(input, options) {
63941
- const { config: config3, xqaDirectory } = options;
63942
- const absolutePath = import_node_path15.default.resolve(input.specFile);
64266
+ const resolvedSpecFile = await resolveSpecFile(input.specFile, options.xqaDirectory);
64267
+ if (resolvedSpecFile === void 0) {
64268
+ return;
64269
+ }
64270
+ const absolutePath = import_node_path15.default.resolve(resolvedSpecFile);
63943
64271
  const frontmatter = readAndParseSpec(absolutePath);
63944
64272
  if (frontmatter === void 0) {
63945
64273
  return;
63946
64274
  }
63947
- const slug = deriveSpecSlug(absolutePath);
63948
- const result = await runPipeline2(
63949
- buildPipelineConfig2(input, {
63950
- config: config3,
63951
- xqaDirectory,
63952
- absolutePath,
63953
- entry: frontmatter.entry,
63954
- slug
63955
- })
63956
- );
63957
- result.match((output) => {
63958
- handleSpecSuccess(xqaDirectory, output);
63959
- }, handleSpecError);
64275
+ const context = await buildContext(options, {
64276
+ absolutePath,
64277
+ entry: frontmatter.entry,
64278
+ timeout: frontmatter.timeout
64279
+ });
64280
+ if (context === void 0) {
64281
+ return;
64282
+ }
64283
+ await executeSpec(input, context);
63960
64284
  }
63961
64285
 
63962
64286
  // src/config.ts
63963
64287
  var import_node_path16 = __toESM(require("node:path"), 1);
63964
- var import_node_url = require("node:url");
64288
+ var import_node_url2 = require("node:url");
63965
64289
  var import_dotenv = __toESM(require_main(), 1);
63966
- var import_neverthrow40 = __toESM(require_index_cjs(), 1);
64290
+ var import_neverthrow41 = __toESM(require_index_cjs(), 1);
63967
64291
 
63968
64292
  // ../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/external.js
63969
64293
  var external_exports2 = {};
@@ -68016,7 +68340,7 @@ var configSchema = external_exports2.object({
68016
68340
  });
68017
68341
 
68018
68342
  // src/config.ts
68019
- var packageDirectory = import_node_path16.default.dirname((0, import_node_url.fileURLToPath)(__importMetaUrl));
68343
+ var packageDirectory = import_node_path16.default.dirname((0, import_node_url2.fileURLToPath)(__importMetaUrl));
68020
68344
  function loadConfig() {
68021
68345
  (0, import_dotenv.config)({ path: import_node_path16.default.resolve(packageDirectory, "..", ".env.local") });
68022
68346
  const result = configSchema.safeParse(process.env);
@@ -68024,15 +68348,34 @@ function loadConfig() {
68024
68348
  const messages = result.error.issues.map(
68025
68349
  (issue2) => ` - ${issue2.path.join(".")}: ${issue2.message}`
68026
68350
  );
68027
- return (0, import_neverthrow40.err)({ type: "INVALID_CONFIG", message: `Configuration error:
68351
+ return (0, import_neverthrow41.err)({ type: "INVALID_CONFIG", message: `Configuration error:
68028
68352
  ${messages.join("\n")}` });
68029
68353
  }
68030
- return (0, import_neverthrow40.ok)(result.data);
68354
+ return (0, import_neverthrow41.ok)(result.data);
68355
+ }
68356
+
68357
+ // src/core/parse-verbose.ts
68358
+ function parseVerboseOption(value) {
68359
+ if (value === void 0 || value === "all") {
68360
+ return new Set(ALL_VERBOSE_CATEGORIES);
68361
+ }
68362
+ if (value === "") {
68363
+ throw new InvalidArgumentError("--verbose requires categories or no value for all");
68364
+ }
68365
+ const requested = value.split(",").map((category) => category.trim().toLowerCase());
68366
+ const invalid = requested.filter((category) => !ALL_VERBOSE_CATEGORIES.has(category));
68367
+ const validList = [...ALL_VERBOSE_CATEGORIES].join(", ");
68368
+ if (invalid.length > 0) {
68369
+ const names = invalid.map((name) => `"${name}"`).join(", ");
68370
+ const label = invalid.length === 1 ? "category" : "categories";
68371
+ throw new InvalidArgumentError(`Unknown verbose ${label}: ${names}. Valid: ${validList}`);
68372
+ }
68373
+ return new Set(requested);
68031
68374
  }
68032
68375
 
68033
68376
  // src/pid-lock.ts
68034
68377
  var import_node_fs8 = require("node:fs");
68035
- var import_neverthrow41 = __toESM(require_index_cjs(), 1);
68378
+ var import_neverthrow42 = __toESM(require_index_cjs(), 1);
68036
68379
  var PID_FILE = "/tmp/xqa.pid";
68037
68380
  var SIGINT_EXIT_CODE = 130;
68038
68381
  var SIGTERM_EXIT_CODE = 143;
@@ -68041,7 +68384,7 @@ var HARD_TIMEOUT_MS = 1e4;
68041
68384
  var cleanup = () => {
68042
68385
  (0, import_node_fs8.rmSync)(PID_FILE, { force: true });
68043
68386
  };
68044
- var checkProcessRunning = (0, import_neverthrow41.fromThrowable)(
68387
+ var checkProcessRunning = (0, import_neverthrow42.fromThrowable)(
68045
68388
  (pid) => {
68046
68389
  process.kill(pid, 0);
68047
68390
  return true;
@@ -68107,17 +68450,17 @@ function acquireLock() {
68107
68450
  // src/shell/xqa-directory.ts
68108
68451
  var import_node_fs9 = require("node:fs");
68109
68452
  var import_node_path17 = __toESM(require("node:path"), 1);
68110
- var import_neverthrow42 = __toESM(require_index_cjs(), 1);
68453
+ var import_neverthrow43 = __toESM(require_index_cjs(), 1);
68111
68454
  function findXqaDirectory(startDirectory) {
68112
68455
  let current = startDirectory;
68113
68456
  for (; ; ) {
68114
68457
  const candidate = import_node_path17.default.join(current, ".xqa");
68115
68458
  if ((0, import_node_fs9.existsSync)(candidate)) {
68116
- return (0, import_neverthrow42.ok)(candidate);
68459
+ return (0, import_neverthrow43.ok)(candidate);
68117
68460
  }
68118
68461
  const parent = import_node_path17.default.dirname(current);
68119
68462
  if (parent === current) {
68120
- return (0, import_neverthrow42.err)({ type: "XQA_NOT_INITIALIZED" });
68463
+ return (0, import_neverthrow43.err)({ type: "XQA_NOT_INITIALIZED" });
68121
68464
  }
68122
68465
  current = parent;
68123
68466
  }
@@ -68145,7 +68488,11 @@ program2.name("xqa").description("AI-powered QA agent CLI");
68145
68488
  program2.command("init").description("Initialize a new xqa project in the current directory").action(() => {
68146
68489
  runInitCommand();
68147
68490
  });
68148
- program2.command("explore").description("Run the explorer agent; omit prompt for a full breadth-first sweep").argument("[prompt]", "Optional focus hint for the explorer; omit for a full breadth-first sweep").option("--verbose", "Log tool call results").action((prompt, options) => {
68491
+ program2.command("explore").description("Run the explorer agent; omit prompt for a full breadth-first sweep").argument("[prompt]", "Optional focus hint for the explorer; omit for a full breadth-first sweep").option(
68492
+ "-v, --verbose [categories]",
68493
+ "Verbose output [prompt,tools,screen,memory] (default: all)",
68494
+ parseVerboseOption
68495
+ ).action((prompt, options) => {
68149
68496
  const xqaDirectory = resolveXqaDirectory();
68150
68497
  runExploreCommand(
68151
68498
  { prompt, verbose: options.verbose, signal: controller.signal },
@@ -68162,10 +68509,14 @@ program2.command("review").description("Review findings and mark false positives
68162
68509
  const xqaDirectory = resolveXqaDirectory();
68163
68510
  void runReviewCommand(findingsPath, xqaDirectory);
68164
68511
  });
68165
- program2.command("spec").description("Run the explorer agent against a spec file").argument("<spec-file>", "Path to the spec markdown file").option("--verbose", "Log tool call results").action((specFile, options) => {
68512
+ program2.command("spec").description("Run the explorer agent against a spec file").argument("[spec-file]", "Path to the spec markdown file; omit to pick interactively").option(
68513
+ "-v, --verbose [categories]",
68514
+ "Verbose output [prompt,tools,screen,memory] (default: all)",
68515
+ parseVerboseOption
68516
+ ).action((specFile, options) => {
68166
68517
  const xqaDirectory = resolveXqaDirectory();
68167
68518
  void runSpecCommand(
68168
- { specFile, verbose: options.verbose ?? false, signal: controller.signal },
68519
+ { specFile, verbose: options.verbose, signal: controller.signal },
68169
68520
  { config: config2, xqaDirectory }
68170
68521
  );
68171
68522
  });