@exodus/xqa 1.2.3 → 1.3.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;
@@ -48106,7 +48106,7 @@ function dismissalsPath(baseDirectory, override) {
48106
48106
  }
48107
48107
 
48108
48108
  // ../../packages/pipeline/dist/index.js
48109
- var import_neverthrow27 = __toESM(require_index_cjs(), 1);
48109
+ var import_neverthrow28 = __toESM(require_index_cjs(), 1);
48110
48110
  var import_promises14 = require("node:timers/promises");
48111
48111
 
48112
48112
  // ../../agents/analyser/dist/index.js
@@ -55763,10 +55763,10 @@ function runConsolidator(input, config3) {
55763
55763
  }
55764
55764
 
55765
55765
  // ../../packages/pipeline/dist/index.js
55766
- var import_neverthrow28 = __toESM(require_index_cjs(), 1);
55767
55766
  var import_neverthrow29 = __toESM(require_index_cjs(), 1);
55768
- var import_node_fs3 = require("node:fs");
55769
55767
  var import_neverthrow30 = __toESM(require_index_cjs(), 1);
55768
+ var import_node_fs3 = require("node:fs");
55769
+ var import_neverthrow31 = __toESM(require_index_cjs(), 1);
55770
55770
  var import_promises15 = require("node:timers/promises");
55771
55771
 
55772
55772
  // ../../agents/explorer/dist/index.js
@@ -55776,16 +55776,17 @@ var import_node_path4 = __toESM(require("node:path"), 1);
55776
55776
  var import_neverthrow14 = __toESM(require_index_cjs(), 1);
55777
55777
  var import_promises8 = require("node:fs/promises");
55778
55778
  var import_neverthrow15 = __toESM(require_index_cjs(), 1);
55779
- var import_promises9 = require("node:timers/promises");
55780
55779
  var import_neverthrow16 = __toESM(require_index_cjs(), 1);
55780
+ var import_promises9 = require("node:timers/promises");
55781
55781
  var import_neverthrow17 = __toESM(require_index_cjs(), 1);
55782
+ var import_neverthrow18 = __toESM(require_index_cjs(), 1);
55782
55783
  var import_promises10 = require("node:fs/promises");
55783
55784
  var import_node_path5 = __toESM(require("node:path"), 1);
55784
- var import_neverthrow18 = __toESM(require_index_cjs(), 1);
55785
+ var import_neverthrow19 = __toESM(require_index_cjs(), 1);
55785
55786
  var import_node_child_process4 = require("node:child_process");
55786
55787
  var import_promises11 = require("node:fs/promises");
55787
55788
  var import_node_path6 = __toESM(require("node:path"), 1);
55788
- var import_neverthrow19 = __toESM(require_index_cjs(), 1);
55789
+ var import_neverthrow20 = __toESM(require_index_cjs(), 1);
55789
55790
  async function runFfmpeg(arguments_) {
55790
55791
  const { promise: promise2, resolve, reject } = Promise.withResolvers();
55791
55792
  (0, import_node_child_process3.execFile)("ffmpeg", arguments_, (error48) => {
@@ -55883,6 +55884,277 @@ function speedUpVideo({
55883
55884
  () => outputPath
55884
55885
  );
55885
55886
  }
55887
+ var DEV_ENVIRONMENT_SECTION = `## Environment
55888
+
55889
+ This is a development build. Debug overlays and internal messages are expected artifacts \u2014 do not report them as findings.`;
55890
+ var WORKING_STATE_SECTION = `## Working State
55891
+
55892
+ At every reasoning step, maintain a mental ledger:
55893
+ - VISITED: screen names confirmed via \`view_ui\` this session
55894
+ - QUEUE: screen names seen as reachable but not yet explored
55895
+ - PATH: your current navigation stack from root (e.g. Home > Settings > Privacy)
55896
+
55897
+ Consult the ledger before every action. Always prefer navigating to a QUEUE screen over a VISITED one.`;
55898
+ 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`;
55899
+ 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`;
55900
+ 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`;
55901
+ var WHAT_TO_TEST_SECTION = `## What to Test
55902
+
55903
+ Test navigation elements first, interactions second.
55904
+
55905
+ **Tier 1 \u2014 test every one, every time:**
55906
+ - Tab bar items, drawer menu items, hamburger menus
55907
+ - Back buttons, close buttons (X), modal dismiss controls
55908
+ - Bottom sheet handles and drag gestures
55909
+ - Navigation links and "Go to X" CTAs
55910
+
55911
+ **Tier 2 \u2014 test one representative per type per screen:**
55912
+ - Primary action buttons
55913
+ - Form inputs and toggles
55914
+ - Segmented controls and dropdowns
55915
+
55916
+ **Tier 3 \u2014 skip unless visibly broken:**
55917
+ - Static labels, decorative images, dividers
55918
+
55919
+ If an interaction produces no observable change, retry once before flagging.`;
55920
+ var DEAD_END_SECTION = `## Dead End and Modal Detection
55921
+
55922
+ **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.
55923
+
55924
+ **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.`;
55925
+ var SPEC_MODE_TEMPLATE = (specContent, options) => {
55926
+ const appSection = options.userPrompt ? `## Application
55927
+
55928
+ ${options.userPrompt}
55929
+
55930
+ ` : "";
55931
+ const environmentSection = options.buildEnv === "dev" ? `
55932
+
55933
+ ${DEV_ENVIRONMENT_SECTION}` : "";
55934
+ 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.
55935
+
55936
+ Verify app against specs below.
55937
+
55938
+ ${appSection}${WORKING_STATE_SECTION}
55939
+
55940
+ ## Rules
55941
+
55942
+ - ALWAYS call \`view_ui\` after every action before deciding what to do next \u2014 it is your only way to observe the screen
55943
+ - ${BACK_NAV_RULE.slice(2)}
55944
+ - Before selecting any action, prefer navigating to a QUEUE screen over re-exploring a VISITED one
55945
+ - ${STUCK_LOOP_RULE.slice(2)}
55946
+ - ${CLIPPED_ELEMENT_RULE.slice(2)}
55947
+ - 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
55948
+ - Flag unexpected navigation failures, broken interactions, or crash dialogs encountered during step execution, even if not listed as assertions
55949
+
55950
+ ## Exploration Strategy
55951
+
55952
+ 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.
55953
+
55954
+ ${WHAT_TO_TEST_SECTION}
55955
+
55956
+ ${DEAD_END_SECTION}
55957
+
55958
+ ## Specs
55959
+
55960
+ ${specContent}${environmentSection}
55961
+
55962
+ ## Output
55963
+
55964
+ 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.`;
55965
+ };
55966
+ var FREESTYLE_TEMPLATE = (options) => {
55967
+ const { userPrompt, buildEnv } = options ?? {};
55968
+ const appSection = userPrompt ? `## Application
55969
+
55970
+ ${userPrompt}
55971
+
55972
+ ` : "";
55973
+ const environmentSection = buildEnv === "dev" ? `
55974
+
55975
+ ${DEV_ENVIRONMENT_SECTION}` : "";
55976
+ 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.
55977
+
55978
+ ${appSection}${WORKING_STATE_SECTION}
55979
+
55980
+ ## Rules
55981
+
55982
+ - ALWAYS call \`view_ui\` after every action before deciding what to do next \u2014 it is your only way to observe the screen
55983
+ - ${BACK_NAV_RULE.slice(2)}
55984
+ - Before selecting any action, prefer navigating to a QUEUE screen over re-exploring a VISITED one
55985
+ - ${STUCK_LOOP_RULE.slice(2)}
55986
+ - ${CLIPPED_ELEMENT_RULE.slice(2)}
55987
+
55988
+ ## Exploration Strategy
55989
+
55990
+ 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.
55991
+
55992
+ ${WHAT_TO_TEST_SECTION}
55993
+
55994
+ ${DEAD_END_SECTION}${environmentSection}
55995
+
55996
+ ## Output
55997
+
55998
+ 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.`;
55999
+ };
56000
+ function generateExplorerPrompt({
56001
+ mode,
56002
+ specs,
56003
+ userPrompt,
56004
+ buildEnv
56005
+ }) {
56006
+ return mode === "spec" ? buildSpecModePrompt(specs, { userPrompt, buildEnv }) : FREESTYLE_TEMPLATE({ userPrompt, buildEnv });
56007
+ }
56008
+ function renderStep(step, index) {
56009
+ const stepNumber = String(index + 1);
56010
+ const base = `${stepNumber}. ${step.action}`;
56011
+ return step.assertion === void 0 ? base : `${base} \u2192 ${step.assertion}`;
56012
+ }
56013
+ function collectAssertions(spec) {
56014
+ return spec.assertions;
56015
+ }
56016
+ function renderSpec(spec) {
56017
+ const stepsBlock = spec.steps.map((step, index) => renderStep(step, index)).join("\n");
56018
+ const allAssertions = collectAssertions(spec);
56019
+ const assertionsBlock = allAssertions.length > 0 ? `
56020
+
56021
+ **Assertions**
56022
+ ${allAssertions.map((assertion) => `- ${assertion}`).join("\n")}` : "";
56023
+ return `### ${spec.name}
56024
+
56025
+ **Setup**
56026
+ ${spec.setup}
56027
+
56028
+ **Steps**
56029
+ ${stepsBlock}${assertionsBlock}`;
56030
+ }
56031
+ function buildSpecModePrompt(specs, options) {
56032
+ const specContent = specs.map((spec) => renderSpec(spec)).join("\n\n---\n\n");
56033
+ return SPEC_MODE_TEMPLATE(specContent, options);
56034
+ }
56035
+ var FRONTMATTER_FENCE = "---";
56036
+ var INLINE_ASSERTION_DELIMITER = " \u2192 ";
56037
+ var NUMBERED_STEP_PREFIX = /^\d+\.\s+/;
56038
+ function parseTagsValue(value) {
56039
+ if (!value.startsWith("[") || !value.endsWith("]")) {
56040
+ return (0, import_neverthrow16.err)({ type: "MALFORMED_FRONTMATTER", cause: `Invalid tags format: ${value}` });
56041
+ }
56042
+ return (0, import_neverthrow16.ok)(
56043
+ value.slice(1, -1).split(",").map((tag) => tag.trim()).filter((tag) => tag.length > 0)
56044
+ );
56045
+ }
56046
+ function parseTimeoutValue(value) {
56047
+ const parsed = Number(value);
56048
+ if (Number.isNaN(parsed) || parsed <= 0) {
56049
+ return (0, import_neverthrow16.err)({ type: "MALFORMED_FRONTMATTER", cause: `Invalid timeout: ${value}` });
56050
+ }
56051
+ return (0, import_neverthrow16.ok)(parsed);
56052
+ }
56053
+ function parseFrontmatterLine(line) {
56054
+ const colonIndex = line.indexOf(":");
56055
+ if (colonIndex === -1) {
56056
+ return (0, import_neverthrow16.err)({ type: "MALFORMED_FRONTMATTER", cause: `Invalid line: ${line}` });
56057
+ }
56058
+ const key = line.slice(0, colonIndex).trim();
56059
+ const value = line.slice(colonIndex + 1).trim();
56060
+ switch (key) {
56061
+ case "description": {
56062
+ return (0, import_neverthrow16.ok)({ description: value });
56063
+ }
56064
+ case "tags": {
56065
+ return parseTagsValue(value).map((tags) => ({ tags }));
56066
+ }
56067
+ case "timeout": {
56068
+ return parseTimeoutValue(value).map((timeout) => ({ timeout }));
56069
+ }
56070
+ default: {
56071
+ return (0, import_neverthrow16.ok)({});
56072
+ }
56073
+ }
56074
+ }
56075
+ function normalizeFrontmatter(raw) {
56076
+ return {
56077
+ description: raw.description,
56078
+ tags: raw.tags ?? [],
56079
+ timeout: raw.timeout
56080
+ };
56081
+ }
56082
+ function emptyFrontmatter() {
56083
+ return { description: void 0, tags: [], timeout: void 0 };
56084
+ }
56085
+ function mergePartials(partials) {
56086
+ return Object.fromEntries(partials.flatMap((partial2) => Object.entries(partial2)));
56087
+ }
56088
+ function combineFrontmatterLines(lines) {
56089
+ const lineResults = lines.filter((line) => line.trim().length > 0).map((line) => parseFrontmatterLine(line));
56090
+ return import_neverthrow16.Result.combine(lineResults).map((partials) => mergePartials(partials)).map((merged) => normalizeFrontmatter(merged));
56091
+ }
56092
+ function extractFrontmatter(content) {
56093
+ const trimmed = content.trimStart();
56094
+ if (!trimmed.startsWith(FRONTMATTER_FENCE)) {
56095
+ return (0, import_neverthrow16.ok)({ frontmatter: emptyFrontmatter(), body: content });
56096
+ }
56097
+ const afterOpenFence = trimmed.slice(FRONTMATTER_FENCE.length);
56098
+ const closeIndex = afterOpenFence.indexOf(`
56099
+ ${FRONTMATTER_FENCE}`);
56100
+ if (closeIndex === -1) {
56101
+ return (0, import_neverthrow16.err)({ type: "MALFORMED_FRONTMATTER", cause: "Unclosed frontmatter fence" });
56102
+ }
56103
+ const rawFrontmatter = afterOpenFence.slice(0, closeIndex);
56104
+ const body = afterOpenFence.slice(closeIndex + FRONTMATTER_FENCE.length + 1);
56105
+ return combineFrontmatterLines(rawFrontmatter.split("\n")).map((frontmatter) => ({
56106
+ frontmatter,
56107
+ body
56108
+ }));
56109
+ }
56110
+ function splitIntoSections(body) {
56111
+ const parts = ("\n" + body).split(/\n## /).slice(1);
56112
+ return Object.fromEntries(
56113
+ parts.map((part) => {
56114
+ const newlineIndex = part.indexOf("\n");
56115
+ const heading = newlineIndex === -1 ? part.trim() : part.slice(0, newlineIndex).trim();
56116
+ const sectionContent = newlineIndex === -1 ? "" : part.slice(newlineIndex + 1).trim();
56117
+ return [heading, sectionContent];
56118
+ })
56119
+ );
56120
+ }
56121
+ function parseStep(line) {
56122
+ const withoutNumber = line.replace(NUMBERED_STEP_PREFIX, "");
56123
+ const delimiterIndex = withoutNumber.indexOf(INLINE_ASSERTION_DELIMITER);
56124
+ if (delimiterIndex === -1) {
56125
+ return { action: withoutNumber.trim(), assertion: void 0 };
56126
+ }
56127
+ return {
56128
+ action: withoutNumber.slice(0, delimiterIndex).trim(),
56129
+ assertion: withoutNumber.slice(delimiterIndex + INLINE_ASSERTION_DELIMITER.length).trim()
56130
+ };
56131
+ }
56132
+ function parseSteps(stepsSection) {
56133
+ return stepsSection.split("\n").filter((line) => NUMBERED_STEP_PREFIX.test(line)).map((line) => parseStep(line));
56134
+ }
56135
+ function parseAssertions(assertionsSection) {
56136
+ return assertionsSection.split("\n").filter((line) => line.startsWith("- ")).map((line) => line.slice(2).trim());
56137
+ }
56138
+ function parseTestSpec(name, content) {
56139
+ return extractFrontmatter(content).andThen(({ frontmatter, body }) => {
56140
+ const sections = splitIntoSections(body);
56141
+ const setup = sections.Setup;
56142
+ if (setup === void 0) {
56143
+ return (0, import_neverthrow16.err)({ type: "MISSING_SETUP_SECTION" });
56144
+ }
56145
+ const stepsSection = sections.Steps;
56146
+ if (stepsSection === void 0) {
56147
+ return (0, import_neverthrow16.err)({ type: "MISSING_STEPS_SECTION" });
56148
+ }
56149
+ return (0, import_neverthrow16.ok)({
56150
+ name,
56151
+ frontmatter,
56152
+ setup,
56153
+ steps: parseSteps(stepsSection),
56154
+ assertions: sections.Assertions === void 0 ? [] : parseAssertions(sections.Assertions)
56155
+ });
56156
+ });
56157
+ }
55886
56158
  var VIEW_UI_TOOL_NAME = "mcp__perception__view_ui";
55887
56159
  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
56160
 
@@ -55898,7 +56170,7 @@ function deriveScreenLabel(tree, stepIndex) {
55898
56170
  }
55899
56171
  async function persistScreenshot(params) {
55900
56172
  const screenshotPath = import_node_path5.default.join(params.screenshotsDirectory, `${params.screenLabel}.png`);
55901
- const safeWriteFile = (0, import_neverthrow18.fromAsyncThrowable)(
56173
+ const safeWriteFile = (0, import_neverthrow19.fromAsyncThrowable)(
55902
56174
  import_promises10.writeFile,
55903
56175
  (cause) => ({ type: "WRITE_FAILED", cause })
55904
56176
  );
@@ -55963,7 +56235,7 @@ function buildSnapshotRecord(rawElements, { stepIndex, context }) {
55963
56235
  }
55964
56236
  async function handleScreenshotData(data, params) {
55965
56237
  if (params.context.screenshotsDir !== void 0) {
55966
- const safeWrite2 = (0, import_neverthrow18.fromAsyncThrowable)(
56238
+ const safeWrite2 = (0, import_neverthrow19.fromAsyncThrowable)(
55967
56239
  import_promises10.writeFile,
55968
56240
  (cause) => ({ type: "WRITE_FAILED", cause })
55969
56241
  );
@@ -56133,23 +56405,23 @@ function processMessage(message, state) {
56133
56405
  }
56134
56406
  if (message.type === "result") {
56135
56407
  if (message.subtype !== "success" && !state.timedOut.value && !state.aborted.value) {
56136
- return (0, import_neverthrow17.err)(message.errors.join("; "));
56408
+ return (0, import_neverthrow18.err)(message.errors.join("; "));
56137
56409
  }
56138
- return (0, import_neverthrow17.ok)(true);
56410
+ return (0, import_neverthrow18.ok)(true);
56139
56411
  }
56140
- return (0, import_neverthrow17.ok)(false);
56412
+ return (0, import_neverthrow18.ok)(false);
56141
56413
  }
56142
56414
  async function processMessages(queryRunner, state) {
56143
56415
  for await (const message of queryRunner) {
56144
56416
  const result = processMessage(message, state);
56145
56417
  if (result.isErr()) {
56146
- return (0, import_neverthrow17.err)(result.error);
56418
+ return (0, import_neverthrow18.err)(result.error);
56147
56419
  }
56148
56420
  if (result.value) {
56149
56421
  break;
56150
56422
  }
56151
56423
  }
56152
- return (0, import_neverthrow17.ok)(null);
56424
+ return (0, import_neverthrow18.ok)(null);
56153
56425
  }
56154
56426
  var MessageQueue = class {
56155
56427
  pending = [];
@@ -56409,7 +56681,7 @@ var INTERRUPT_DRAIN_TIMEOUT_MS = 1e4;
56409
56681
  async function interruptOrTimeout(queryRunner) {
56410
56682
  await Promise.race([queryRunner.interrupt(), (0, import_promises9.setTimeout)(INTERRUPT_DRAIN_TIMEOUT_MS)]);
56411
56683
  }
56412
- var safeInterruptOrTimeout = import_neverthrow16.ResultAsync.fromThrowable(
56684
+ var safeInterruptOrTimeout = import_neverthrow17.ResultAsync.fromThrowable(
56413
56685
  interruptOrTimeout,
56414
56686
  (error48) => error48
56415
56687
  );
@@ -56460,18 +56732,18 @@ function startQueryTimers(config3, context) {
56460
56732
  }
56461
56733
  function awaitMessagesAndResolve({ queryRunner, state }, { cleanup: cleanup2, getOutput }) {
56462
56734
  const messagesPromise = processMessages(queryRunner, state);
56463
- return import_neverthrow16.ResultAsync.fromPromise(messagesPromise, String).andThen((innerResult) => {
56735
+ return import_neverthrow17.ResultAsync.fromPromise(messagesPromise, String).andThen((innerResult) => {
56464
56736
  cleanup2();
56465
56737
  if (innerResult.isErr()) {
56466
- return (0, import_neverthrow16.err)(innerResult.error);
56738
+ return (0, import_neverthrow17.err)(innerResult.error);
56467
56739
  }
56468
- return (0, import_neverthrow16.ok)(getOutput());
56740
+ return (0, import_neverthrow17.ok)(getOutput());
56469
56741
  }).orElse((sdkError) => {
56470
56742
  cleanup2();
56471
56743
  if (!state.timedOut.value && !state.aborted.value) {
56472
- return (0, import_neverthrow16.err)(sdkError);
56744
+ return (0, import_neverthrow17.err)(sdkError);
56473
56745
  }
56474
- return (0, import_neverthrow16.ok)(getOutput());
56746
+ return (0, import_neverthrow17.ok)(getOutput());
56475
56747
  });
56476
56748
  }
56477
56749
  function executeQuery({
@@ -56485,9 +56757,9 @@ function executeQuery({
56485
56757
  message: { role: "user", content: prompt },
56486
56758
  parent_tool_use_id: null
56487
56759
  });
56488
- const queryRunnerResult = (0, import_neverthrow16.fromThrowable)(Qs, String)({ prompt: inputQueue, options });
56760
+ const queryRunnerResult = (0, import_neverthrow17.fromThrowable)(Qs, String)({ prompt: inputQueue, options });
56489
56761
  if (queryRunnerResult.isErr()) {
56490
- return (0, import_neverthrow16.errAsync)(queryRunnerResult.error);
56762
+ return (0, import_neverthrow17.errAsync)(queryRunnerResult.error);
56491
56763
  }
56492
56764
  const queryRunner = queryRunnerResult.value;
56493
56765
  const cleanup2 = startQueryTimers(config3, { state, queryRunner, inputQueue, linkedController });
@@ -56502,7 +56774,7 @@ function executeQuery({
56502
56774
  function runQuery(prompt, config3) {
56503
56775
  const outputTools = createOutputTool({ findings: external_exports.array(EXPLORER_FINDING_SCHEMA) });
56504
56776
  const setupPromise = setupQuery(config3, outputTools);
56505
- return import_neverthrow16.ResultAsync.fromPromise(setupPromise, String).andThen(
56777
+ return import_neverthrow17.ResultAsync.fromPromise(setupPromise, String).andThen(
56506
56778
  (setup) => executeQuery({ prompt, config: config3, outputTools, setup })
56507
56779
  );
56508
56780
  }
@@ -56549,25 +56821,25 @@ function direntToSpecEntry(directory, entry) {
56549
56821
  return [];
56550
56822
  }
56551
56823
  function scanDirectory(directory) {
56552
- const safeReaddir = (0, import_neverthrow19.fromAsyncThrowable)(
56824
+ const safeReaddir2 = (0, import_neverthrow20.fromAsyncThrowable)(
56553
56825
  async () => (0, import_promises11.readdir)(directory, { withFileTypes: true }),
56554
56826
  (cause) => ({ type: "DIR_READ_FAILED", dir: directory, cause })
56555
56827
  );
56556
- return safeReaddir().orElse((error48) => isNotFound(error48.cause) ? (0, import_neverthrow19.okAsync)([]) : (0, import_neverthrow19.errAsync)(error48)).map(
56828
+ return safeReaddir2().orElse((error48) => isNotFound(error48.cause) ? (0, import_neverthrow20.okAsync)([]) : (0, import_neverthrow20.errAsync)(error48)).map(
56557
56829
  (entries) => entries.flatMap((entry) => direntToSpecEntry(directory, entry))
56558
56830
  );
56559
56831
  }
56560
56832
  function readEntries(entries) {
56561
- return import_neverthrow19.ResultAsync.combine(entries.map((entry) => readEntry(entry))).map(
56833
+ return import_neverthrow20.ResultAsync.combine(entries.map((entry) => readEntry(entry))).map(
56562
56834
  (results) => results.flat()
56563
56835
  );
56564
56836
  }
56565
56837
  function readEntry(entry) {
56566
- const safeReadFile5 = (0, import_neverthrow19.fromAsyncThrowable)(
56838
+ const safeReadFile5 = (0, import_neverthrow20.fromAsyncThrowable)(
56567
56839
  async () => (0, import_promises11.readFile)(entry.path, "utf8"),
56568
56840
  (cause) => ({ type: "FILE_READ_FAILED", path: entry.path, cause })
56569
56841
  );
56570
- return safeReadFile5().map((content) => [{ name: entry.name, content }]).orElse((error48) => entry.required ? (0, import_neverthrow19.errAsync)(error48) : (0, import_neverthrow19.okAsync)([]));
56842
+ return safeReadFile5().map((content) => [{ name: entry.name, content }]).orElse((error48) => entry.required ? (0, import_neverthrow20.errAsync)(error48) : (0, import_neverthrow20.okAsync)([]));
56571
56843
  }
56572
56844
  function specNameFromPath(filePath) {
56573
56845
  const parts = filePath.split("/");
@@ -56583,132 +56855,6 @@ function filterByNames(specs, specNames) {
56583
56855
  }
56584
56856
  return specs.filter((spec) => specNames.includes(spec.name));
56585
56857
  }
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
56858
  var SPEED_2X = 2;
56713
56859
  var SPEED_4X = 4;
56714
56860
  var ISO_DATE_LENGTH = 10;
@@ -56720,6 +56866,15 @@ function buildPrompt(safeConfig, specs) {
56720
56866
  buildEnv: safeConfig.buildEnv
56721
56867
  });
56722
56868
  }
56869
+ function parseSpecs(resolvedSpecs) {
56870
+ return import_neverthrow15.Result.combine(
56871
+ resolvedSpecs.map(
56872
+ (spec) => parseTestSpec(spec.name, spec.content).mapErr(
56873
+ (cause) => ({ type: "SPEC_PARSE_FAILED", specName: spec.name, cause })
56874
+ )
56875
+ )
56876
+ );
56877
+ }
56723
56878
  function toArtifacts(result, runPaths) {
56724
56879
  return {
56725
56880
  findings: result.findings,
@@ -56757,7 +56912,7 @@ function runPipeline({
56757
56912
  runPaths,
56758
56913
  start
56759
56914
  }) {
56760
- return resolveSpecs(safeConfig).mapErr((cause) => ({ type: "SPEC_RESOLVE_FAILED", cause })).map((specs) => buildPrompt(safeConfig, specs)).map((prompt) => {
56915
+ return resolveSpecs(safeConfig).mapErr((cause) => ({ type: "SPEC_RESOLVE_FAILED", cause })).andThen((specs) => parseSpecs(specs)).map((parsedSpecs) => buildPrompt(safeConfig, parsedSpecs)).map((prompt) => {
56761
56916
  safeConfig.onEvent?.({ type: "SYSTEM_PROMPT", agent: "explorer", prompt });
56762
56917
  return prompt;
56763
56918
  }).andThen((prompt) => collectAndFinalize({ safeConfig, prompt, runPaths, start }));
@@ -56841,17 +56996,17 @@ function runWithRecording(handle, collectOutput) {
56841
56996
  }
56842
56997
 
56843
56998
  // ../../packages/pipeline/dist/index.js
56844
- var import_neverthrow31 = __toESM(require_index_cjs(), 1);
56999
+ var import_neverthrow32 = __toESM(require_index_cjs(), 1);
56845
57000
 
56846
57001
  // ../../agents/inspector/dist/index.js
56847
- var import_neverthrow20 = __toESM(require_index_cjs(), 1);
56848
- var import_promises12 = require("node:fs/promises");
56849
57002
  var import_neverthrow21 = __toESM(require_index_cjs(), 1);
57003
+ var import_promises12 = require("node:fs/promises");
56850
57004
  var import_neverthrow22 = __toESM(require_index_cjs(), 1);
56851
57005
  var import_neverthrow23 = __toESM(require_index_cjs(), 1);
56852
57006
  var import_neverthrow24 = __toESM(require_index_cjs(), 1);
56853
- var import_sharp2 = __toESM(require("sharp"), 1);
56854
57007
  var import_neverthrow25 = __toESM(require_index_cjs(), 1);
57008
+ var import_sharp2 = __toESM(require("sharp"), 1);
57009
+ var import_neverthrow26 = __toESM(require_index_cjs(), 1);
56855
57010
  var import_promises13 = require("node:fs/promises");
56856
57011
  var import_node_path7 = __toESM(require("node:path"), 1);
56857
57012
 
@@ -59480,7 +59635,7 @@ var jsYaml = {
59480
59635
  };
59481
59636
 
59482
59637
  // ../../agents/inspector/dist/index.js
59483
- var import_neverthrow26 = __toESM(require_index_cjs(), 1);
59638
+ var import_neverthrow27 = __toESM(require_index_cjs(), 1);
59484
59639
  var MS_PER_DAY = 864e5;
59485
59640
  function checkStaleness({ lastUpdated, thresholdDays, now }) {
59486
59641
  if (!lastUpdated) {
@@ -59557,7 +59712,7 @@ function mapRawFinding(item) {
59557
59712
  function parseJson(raw) {
59558
59713
  return JSON.parse(raw);
59559
59714
  }
59560
- var safeJsonParse2 = (0, import_neverthrow23.fromThrowable)(parseJson);
59715
+ var safeJsonParse2 = (0, import_neverthrow24.fromThrowable)(parseJson);
59561
59716
  function parseClaudeResponse(raw) {
59562
59717
  const parseResult = safeJsonParse2(raw);
59563
59718
  if (parseResult.isErr()) {
@@ -59584,7 +59739,7 @@ async function downscaleBuffer(buffer) {
59584
59739
  return (0, import_sharp2.default)(buffer).resize({ width: targetWidth }).toBuffer();
59585
59740
  }
59586
59741
  function downscale(buffer) {
59587
- return (0, import_neverthrow24.fromAsyncThrowable)(
59742
+ return (0, import_neverthrow25.fromAsyncThrowable)(
59588
59743
  downscaleBuffer,
59589
59744
  (cause) => ({ type: "CLAUDE_API_FAILED", cause })
59590
59745
  )(buffer);
@@ -59657,7 +59812,7 @@ async function fetchClaudeText({
59657
59812
  return block?.type === "text" ? block.text : "";
59658
59813
  }
59659
59814
  function callClaude(options) {
59660
- return (0, import_neverthrow22.fromAsyncThrowable)(
59815
+ return (0, import_neverthrow23.fromAsyncThrowable)(
59661
59816
  fetchClaudeText,
59662
59817
  (cause) => ({ type: "CLAUDE_API_FAILED", cause })
59663
59818
  )(options);
@@ -59669,9 +59824,9 @@ function toFindings(text) {
59669
59824
  const cleaned = stripCodeFences(text);
59670
59825
  const findings = parseClaudeResponse(cleaned);
59671
59826
  if (!findings) {
59672
- return (0, import_neverthrow22.errAsync)({ type: "CLAUDE_RESPONSE_INVALID", raw: cleaned });
59827
+ return (0, import_neverthrow23.errAsync)({ type: "CLAUDE_RESPONSE_INVALID", raw: cleaned });
59673
59828
  }
59674
- return (0, import_neverthrow22.okAsync)(findings);
59829
+ return (0, import_neverthrow23.okAsync)(findings);
59675
59830
  }
59676
59831
  function buildResolveMessages(screenshotBase64, artboardNames) {
59677
59832
  return [
@@ -59708,12 +59863,12 @@ async function fetchResolveName({
59708
59863
  }
59709
59864
  function resolveArtboard(screenshot, artboardNames) {
59710
59865
  if (artboardNames.length === 0) {
59711
- return (0, import_neverthrow22.okAsync)(void 0);
59866
+ return (0, import_neverthrow23.okAsync)(void 0);
59712
59867
  }
59713
59868
  const anthropic = new Anthropic();
59714
59869
  return downscale(screenshot).andThen((scaled) => {
59715
59870
  const screenshotBase64 = scaled.toString("base64");
59716
- return (0, import_neverthrow22.fromAsyncThrowable)(
59871
+ return (0, import_neverthrow23.fromAsyncThrowable)(
59717
59872
  fetchResolveName,
59718
59873
  (cause) => ({ type: "CLAUDE_API_FAILED", cause })
59719
59874
  )({ anthropic, screenshotBase64, artboardNames }).map((name) => {
@@ -59776,7 +59931,7 @@ var SEVERITY_CONFIDENCE = {
59776
59931
  medium: CONFIDENCE_MEDIUM2,
59777
59932
  low: CONFIDENCE_LOW2
59778
59933
  };
59779
- var safeJsonParse22 = (0, import_neverthrow25.fromThrowable)(JSON.parse);
59934
+ var safeJsonParse22 = (0, import_neverthrow26.fromThrowable)(JSON.parse);
59780
59935
  function buildFindCandidatesMessages(screenshotBase64, artboardNames) {
59781
59936
  return [
59782
59937
  {
@@ -59823,12 +59978,12 @@ async function fetchCandidateNames({
59823
59978
  }
59824
59979
  function findCandidates(screenshot, artboardNames) {
59825
59980
  if (artboardNames.length === 0) {
59826
- return (0, import_neverthrow25.okAsync)([]);
59981
+ return (0, import_neverthrow26.okAsync)([]);
59827
59982
  }
59828
59983
  const anthropic = new Anthropic();
59829
59984
  return downscale(screenshot).andThen((scaled) => {
59830
59985
  const screenshotBase64 = scaled.toString("base64");
59831
- return (0, import_neverthrow25.fromAsyncThrowable)(
59986
+ return (0, import_neverthrow26.fromAsyncThrowable)(
59832
59987
  fetchCandidateNames,
59833
59988
  (cause) => ({ type: "CLAUDE_API_FAILED", cause })
59834
59989
  )({ anthropic, screenshotBase64, artboardNames });
@@ -59906,14 +60061,14 @@ async function fetchDesignContextText({
59906
60061
  function toDesignContextFindings(text) {
59907
60062
  const findings = parseConservativeResponse(text);
59908
60063
  if (!findings) {
59909
- return (0, import_neverthrow25.errAsync)({ type: "CLAUDE_RESPONSE_INVALID", raw: text });
60064
+ return (0, import_neverthrow26.errAsync)({ type: "CLAUDE_RESPONSE_INVALID", raw: text });
59910
60065
  }
59911
- return (0, import_neverthrow25.okAsync)(findings);
60066
+ return (0, import_neverthrow26.okAsync)(findings);
59912
60067
  }
59913
60068
  async function downscaleAll(buffers) {
59914
60069
  return Promise.all(buffers.map(async (buf) => downscaleBuffer(buf)));
59915
60070
  }
59916
- var downscaleAllBuffers = (0, import_neverthrow25.fromAsyncThrowable)(
60071
+ var downscaleAllBuffers = (0, import_neverthrow26.fromAsyncThrowable)(
59917
60072
  downscaleAll,
59918
60073
  (cause) => ({ type: "CLAUDE_API_FAILED", cause })
59919
60074
  );
@@ -59923,7 +60078,7 @@ function compareWithDesignContext(screenshot, artboards) {
59923
60078
  const screenshotBase64 = scaledScreenshot.toString("base64");
59924
60079
  return downscaleAllBuffers(artboards).andThen((scaledArtboards) => {
59925
60080
  const artboardBase64s = scaledArtboards.map((buf) => buf.toString("base64"));
59926
- return (0, import_neverthrow25.fromAsyncThrowable)(
60081
+ return (0, import_neverthrow26.fromAsyncThrowable)(
59927
60082
  fetchDesignContextText,
59928
60083
  (cause) => ({ type: "CLAUDE_API_FAILED", cause })
59929
60084
  )({ anthropic, screenshotBase64, artboardBase64s }).andThen(toDesignContextFindings);
@@ -60156,7 +60311,7 @@ async function initArtboardNames({ designStore, config: config3, state }) {
60156
60311
  return state.artboardNamesPromise;
60157
60312
  }
60158
60313
  function readScreenshot(screenshotPath, stepIndex) {
60159
- return import_neverthrow21.ResultAsync.fromThrowable(
60314
+ return import_neverthrow22.ResultAsync.fromThrowable(
60160
60315
  import_promises12.readFile,
60161
60316
  (cause) => ({ type: "SCREENSHOT_READ_FAILED", stepIndex, cause })
60162
60317
  )(screenshotPath);
@@ -60231,7 +60386,7 @@ function buildInspector(state, context) {
60231
60386
  const { promise: promise2, resolve } = Promise.withResolvers();
60232
60387
  state.resolve = resolve;
60233
60388
  applyTryResolve(state);
60234
- return (0, import_neverthrow20.fromSafePromise)(promise2);
60389
+ return (0, import_neverthrow21.fromSafePromise)(promise2);
60235
60390
  }
60236
60391
  };
60237
60392
  }
@@ -60255,10 +60410,10 @@ async function readAndParseSidecar(sidecarPath) {
60255
60410
  return parseMeta(raw);
60256
60411
  }
60257
60412
  function readSidecarFile(sidecarPath) {
60258
- return (0, import_neverthrow26.fromAsyncThrowable)(
60413
+ return (0, import_neverthrow27.fromAsyncThrowable)(
60259
60414
  readAndParseSidecar,
60260
60415
  () => ({})
60261
- )(sidecarPath).orElse(() => (0, import_neverthrow26.okAsync)({}));
60416
+ )(sidecarPath).orElse(() => (0, import_neverthrow27.okAsync)({}));
60262
60417
  }
60263
60418
  function isEnoent(error48) {
60264
60419
  return error48?.code === "ENOENT";
@@ -60267,13 +60422,13 @@ function wrapFsError(cause) {
60267
60422
  return { type: "FS_ERROR", cause };
60268
60423
  }
60269
60424
  function toFsError(fsError) {
60270
- return (0, import_neverthrow26.errAsync)(fsError);
60425
+ return (0, import_neverthrow27.errAsync)(fsError);
60271
60426
  }
60272
60427
  function missingBuffer() {
60273
- return (0, import_neverthrow26.okAsync)(void 0);
60428
+ return (0, import_neverthrow27.okAsync)(void 0);
60274
60429
  }
60275
60430
  function missingArtboard() {
60276
- return (0, import_neverthrow26.okAsync)(void 0);
60431
+ return (0, import_neverthrow27.okAsync)(void 0);
60277
60432
  }
60278
60433
  var FsDesignStore = class {
60279
60434
  designsDirectory;
@@ -60281,12 +60436,12 @@ var FsDesignStore = class {
60281
60436
  this.designsDirectory = designsDirectory;
60282
60437
  }
60283
60438
  listArtboards() {
60284
- return (0, import_neverthrow26.fromAsyncThrowable)(
60439
+ return (0, import_neverthrow27.fromAsyncThrowable)(
60285
60440
  import_promises13.readdir,
60286
60441
  wrapFsError
60287
60442
  )(this.designsDirectory).orElse((fsError) => {
60288
60443
  if (fsError.type === "FS_ERROR" && isEnoent(fsError.cause)) {
60289
- return (0, import_neverthrow26.okAsync)([]);
60444
+ return (0, import_neverthrow27.okAsync)([]);
60290
60445
  }
60291
60446
  return toFsError(fsError);
60292
60447
  }).map(
@@ -60296,7 +60451,7 @@ var FsDesignStore = class {
60296
60451
  getArtboard(filename) {
60297
60452
  const pngPath = import_node_path7.default.join(this.designsDirectory, `${filename}.png`);
60298
60453
  const sidecarPath = import_node_path7.default.join(this.designsDirectory, `${filename}.meta.yaml`);
60299
- return (0, import_neverthrow26.fromAsyncThrowable)(
60454
+ return (0, import_neverthrow27.fromAsyncThrowable)(
60300
60455
  import_promises13.readFile,
60301
60456
  wrapFsError
60302
60457
  )(pngPath).orElse((fsError) => {
@@ -60323,10 +60478,10 @@ function attemptRetry(options) {
60323
60478
  const { factory, config: config3, delayFunction, onRetry, attempt } = options;
60324
60479
  return factory().orElse((error48) => {
60325
60480
  if (attempt >= config3.maxAttempts) {
60326
- return (0, import_neverthrow29.errAsync)(error48);
60481
+ return (0, import_neverthrow30.errAsync)(error48);
60327
60482
  }
60328
60483
  const delay = config3.baseDelayMs * Math.pow(2, attempt - 1);
60329
- return import_neverthrow29.ResultAsync.fromPromise(
60484
+ return import_neverthrow30.ResultAsync.fromPromise(
60330
60485
  (onRetry?.({ attempt, maxAttempts: config3.maxAttempts, delayMs: delay, error: error48 }), delayFunction(delay)),
60331
60486
  () => error48
60332
60487
  ).andThen(
@@ -60346,7 +60501,7 @@ function withRetry(factory, options) {
60346
60501
  var CONSOLIDATOR_AGENT = "consolidator";
60347
60502
  function analyserFallback(artifacts, onEvent) {
60348
60503
  onEvent?.({ type: "AGENT_FAILED_NON_CRITICAL", agent: "analyser", attempts: RETRY_MAX_ATTEMPTS });
60349
- return (0, import_neverthrow28.okAsync)(artifacts.findings);
60504
+ return (0, import_neverthrow29.okAsync)(artifacts.findings);
60350
60505
  }
60351
60506
  function runAnalyserWithRetry(params) {
60352
60507
  const { artifacts, config: config3, onEvent } = params;
@@ -60376,7 +60531,7 @@ function resolveVisualFindings({
60376
60531
  onEvent
60377
60532
  }) {
60378
60533
  if (config3.analyser === void 0 || config3.signal?.aborted) {
60379
- return (0, import_neverthrow28.okAsync)(artifacts.findings);
60534
+ return (0, import_neverthrow29.okAsync)(artifacts.findings);
60380
60535
  }
60381
60536
  return runAnalyserWithRetry({ artifacts, config: config3, onEvent });
60382
60537
  }
@@ -60386,7 +60541,7 @@ function unmergedFallback(allFindings, onEvent) {
60386
60541
  agent: CONSOLIDATOR_AGENT,
60387
60542
  message: "Consolidation failed, returning unmerged findings"
60388
60543
  });
60389
- return (0, import_neverthrow28.okAsync)({ findings: allFindings, dismissed: [] });
60544
+ return (0, import_neverthrow29.okAsync)({ findings: allFindings, dismissed: [] });
60390
60545
  }
60391
60546
  function mergeWithFallback(options) {
60392
60547
  const {
@@ -60413,7 +60568,7 @@ function consolidate(options) {
60413
60568
  const { artifacts, inspectorFindings, runId, dismissals, config: config3, consolidatorConfig, onEvent } = options;
60414
60569
  return resolveVisualFindings({ artifacts, config: config3, onEvent }).andThen((visualFindings) => {
60415
60570
  if (config3.signal?.aborted) {
60416
- return (0, import_neverthrow28.okAsync)({ findings: artifacts.findings, dismissed: [] });
60571
+ return (0, import_neverthrow29.okAsync)({ findings: artifacts.findings, dismissed: [] });
60417
60572
  }
60418
60573
  return mergeWithFallback({
60419
60574
  artifacts,
@@ -60426,8 +60581,8 @@ function consolidate(options) {
60426
60581
  });
60427
60582
  });
60428
60583
  }
60429
- var safeReadFile = (0, import_neverthrow30.fromThrowable)((filePath) => (0, import_node_fs3.readFileSync)(filePath, "utf8"));
60430
- var safeParseJson = (0, import_neverthrow30.fromThrowable)(JSON.parse);
60584
+ var safeReadFile = (0, import_neverthrow31.fromThrowable)((filePath) => (0, import_node_fs3.readFileSync)(filePath, "utf8"));
60585
+ var safeParseJson = (0, import_neverthrow31.fromThrowable)(JSON.parse);
60431
60586
  function isEnoent2(error48) {
60432
60587
  if (!(error48 instanceof Error)) {
60433
60588
  return false;
@@ -60439,19 +60594,19 @@ function loadDismissals(filePath) {
60439
60594
  const readResult = safeReadFile(filePath);
60440
60595
  if (readResult.isErr()) {
60441
60596
  if (isEnoent2(readResult.error)) {
60442
- return (0, import_neverthrow30.ok)([]);
60597
+ return (0, import_neverthrow31.ok)([]);
60443
60598
  }
60444
- return (0, import_neverthrow30.err)({ type: "DISMISSALS_LOAD_FAILED", cause: readResult.error });
60599
+ return (0, import_neverthrow31.err)({ type: "DISMISSALS_LOAD_FAILED", cause: readResult.error });
60445
60600
  }
60446
60601
  return safeParseJson(readResult.value).mapErr((cause) => ({ type: "DISMISSALS_LOAD_FAILED", cause })).andThen((data) => {
60447
60602
  const store = data;
60448
60603
  if (!Array.isArray(store.dismissed)) {
60449
- return (0, import_neverthrow30.err)({
60604
+ return (0, import_neverthrow31.err)({
60450
60605
  type: "DISMISSALS_LOAD_FAILED",
60451
60606
  cause: "invalid shape: dismissed is not an array"
60452
60607
  });
60453
60608
  }
60454
- return (0, import_neverthrow30.ok)(store.dismissed);
60609
+ return (0, import_neverthrow31.ok)(store.dismissed);
60455
60610
  });
60456
60611
  }
60457
60612
  function toInspectorStepEvent(event) {
@@ -60521,7 +60676,7 @@ function runExplorerWithTeardown(explorerConfig, udid) {
60521
60676
  return runExplorer(explorerConfig).mapErr((cause) => ({ type: "EXPLORER_FAILED", cause })).andThen(
60522
60677
  (artifacts) => disableTouchIndicators(udid).mapErr(toSimulatorError).map(() => artifacts)
60523
60678
  ).orElse(
60524
- (error48) => disableTouchIndicators(udid).mapErr(toSimulatorError).andThen(() => (0, import_neverthrow31.errAsync)(error48)).orElse(() => (0, import_neverthrow31.errAsync)(error48))
60679
+ (error48) => disableTouchIndicators(udid).mapErr(toSimulatorError).andThen(() => (0, import_neverthrow32.errAsync)(error48)).orElse(() => (0, import_neverthrow32.errAsync)(error48))
60525
60680
  );
60526
60681
  }
60527
60682
  function runExplorerWithRetry(options) {
@@ -60547,12 +60702,12 @@ async function drainAfterExplorer(options) {
60547
60702
  inspector?.close();
60548
60703
  const inspectorFindings = inspector ? await collectInspectorFindings({ inspector, onEvent, totalSteps: enqueuedCount.value }) : [];
60549
60704
  if (explorerResult.isErr()) {
60550
- return (0, import_neverthrow31.err)(explorerResult.error);
60705
+ return (0, import_neverthrow32.err)(explorerResult.error);
60551
60706
  }
60552
- return (0, import_neverthrow31.ok)({ artifacts: explorerResult.value, inspectorFindings });
60707
+ return (0, import_neverthrow32.ok)({ artifacts: explorerResult.value, inspectorFindings });
60553
60708
  }
60554
60709
  function runExplorerAndDrain(options) {
60555
- return new import_neverthrow31.ResultAsync(drainAfterExplorer(options));
60710
+ return new import_neverthrow32.ResultAsync(drainAfterExplorer(options));
60556
60711
  }
60557
60712
  var require2 = (0, import_node_module.createRequire)(__importMetaUrl);
60558
60713
  function createMobileMcpServer() {
@@ -60570,7 +60725,7 @@ function runAnalysis(artifacts, config3) {
60570
60725
  }
60571
60726
  var ISO_DATE_LENGTH2 = 10;
60572
60727
  var RUN_ID_PAD_LENGTH = 4;
60573
- var safeReaddirSync = (0, import_neverthrow27.fromThrowable)((directory) => (0, import_node_fs2.readdirSync)(directory));
60728
+ var safeReaddirSync = (0, import_neverthrow28.fromThrowable)((directory) => (0, import_node_fs2.readdirSync)(directory));
60574
60729
  function nextRunId(outputDirectory, date5) {
60575
60730
  const entries = safeReaddirSync(`${outputDirectory}/${date5}`).unwrapOr([]);
60576
60731
  let max = 0;
@@ -60582,7 +60737,7 @@ function nextRunId(outputDirectory, date5) {
60582
60737
  }
60583
60738
  return String(max + 1).padStart(RUN_ID_PAD_LENGTH, "0");
60584
60739
  }
60585
- var writeOutputFile = (0, import_neverthrow27.fromThrowable)(
60740
+ var writeOutputFile = (0, import_neverthrow28.fromThrowable)(
60586
60741
  (params) => {
60587
60742
  const { findingsPath, outputDirectory, json: json3 } = params;
60588
60743
  (0, import_node_fs2.mkdirSync)(outputDirectory, { recursive: true });
@@ -60595,15 +60750,15 @@ function validatePipelineConfig(config3) {
60595
60750
  const runId = config3.runId ?? nextRunId(config3.outputDir, date5);
60596
60751
  const runPathsResult = resolveRunPaths({ outputDirectory: config3.outputDir, runId, date: date5 });
60597
60752
  if (runPathsResult.isErr()) {
60598
- return (0, import_neverthrow27.err)({ type: "RUN_PATHS_FAILED", cause: runPathsResult.error });
60753
+ return (0, import_neverthrow28.err)({ type: "RUN_PATHS_FAILED", cause: runPathsResult.error });
60599
60754
  }
60600
60755
  const dismissalsResult = loadDismissals(
60601
60756
  dismissalsPath(config3.outputDir, process.env.QA_DISMISSALS_PATH)
60602
60757
  );
60603
60758
  if (dismissalsResult.isErr()) {
60604
- return (0, import_neverthrow27.err)(dismissalsResult.error);
60759
+ return (0, import_neverthrow28.err)(dismissalsResult.error);
60605
60760
  }
60606
- return (0, import_neverthrow27.ok)({ runId, date: date5, runPaths: runPathsResult.value, dismissals: dismissalsResult.value });
60761
+ return (0, import_neverthrow28.ok)({ runId, date: date5, runPaths: runPathsResult.value, dismissals: dismissalsResult.value });
60607
60762
  }
60608
60763
  function buildExplorerConfig({
60609
60764
  config: config3,
@@ -60638,11 +60793,11 @@ function buildOutput(consolidationResult, options) {
60638
60793
  function buildPipelineSetup(config3) {
60639
60794
  const validatedResult = validatePipelineConfig(config3);
60640
60795
  if (validatedResult.isErr()) {
60641
- return (0, import_neverthrow27.err)(validatedResult.error);
60796
+ return (0, import_neverthrow28.err)(validatedResult.error);
60642
60797
  }
60643
60798
  const { runId, date: date5, runPaths, dismissals } = validatedResult.value;
60644
60799
  const { inspector, explorerOnEvent, enqueuedCount } = buildInspectorSetup(config3);
60645
- return (0, import_neverthrow27.ok)({
60800
+ return (0, import_neverthrow28.ok)({
60646
60801
  runId,
60647
60802
  udid: config3.simulatorUdid ?? "booted",
60648
60803
  runPaths,
@@ -60686,19 +60841,19 @@ function executePipeline(setup, config3) {
60686
60841
  function runPipeline2(config3) {
60687
60842
  const setupResult = buildPipelineSetup(config3);
60688
60843
  if (setupResult.isErr()) {
60689
- return (0, import_neverthrow27.errAsync)(setupResult.error);
60844
+ return (0, import_neverthrow28.errAsync)(setupResult.error);
60690
60845
  }
60691
60846
  return executePipeline(setupResult.value, config3);
60692
60847
  }
60693
60848
 
60694
60849
  // src/commands/analyse-command.ts
60695
- var import_neverthrow32 = __toESM(require_index_cjs(), 1);
60850
+ var import_neverthrow33 = __toESM(require_index_cjs(), 1);
60696
60851
  var JSON_INDENT = 2;
60697
60852
  function buildArtifacts(videoPath) {
60698
60853
  return { videoPath, videoPath2x: "", videoPath4x: videoPath, findings: [], snapshots: [] };
60699
60854
  }
60700
60855
  async function checkVideoPathExists(videoPath) {
60701
- const safeAccess = (0, import_neverthrow32.fromAsyncThrowable)(import_promises16.access, () => ({ type: "FILE_NOT_FOUND" }));
60856
+ const safeAccess = (0, import_neverthrow33.fromAsyncThrowable)(import_promises16.access, () => ({ type: "FILE_NOT_FOUND" }));
60702
60857
  const result = await safeAccess(videoPath);
60703
60858
  return result.isOk();
60704
60859
  }
@@ -60742,7 +60897,7 @@ async function runAnalyseCommand(videoPath, config3) {
60742
60897
  }
60743
60898
 
60744
60899
  // src/core/completion-generator.ts
60745
- var import_neverthrow33 = __toESM(require_index_cjs(), 1);
60900
+ var import_neverthrow34 = __toESM(require_index_cjs(), 1);
60746
60901
  function extractLongFlags(flags) {
60747
60902
  return flags.split(/[\s,]+/).filter((token) => token.startsWith("--"));
60748
60903
  }
@@ -60832,9 +60987,9 @@ complete -F _xqa_completion xqa`;
60832
60987
  }
60833
60988
  function generateCompletion(commands, shell) {
60834
60989
  if (shell !== "bash" && shell !== "zsh") {
60835
- return (0, import_neverthrow33.err)({ type: "UNSUPPORTED_SHELL", shell });
60990
+ return (0, import_neverthrow34.err)({ type: "UNSUPPORTED_SHELL", shell });
60836
60991
  }
60837
- return (0, import_neverthrow33.ok)(shell === "zsh" ? generateZshCompletion(commands) : generateBashCompletion(commands));
60992
+ return (0, import_neverthrow34.ok)(shell === "zsh" ? generateZshCompletion(commands) : generateBashCompletion(commands));
60838
60993
  }
60839
60994
 
60840
60995
  // src/commands/completion-command.ts
@@ -60893,16 +61048,16 @@ var DEFAULT_ABORT_EXIT_CODE = 130;
60893
61048
  // src/core/last-path.ts
60894
61049
  var import_node_fs4 = require("node:fs");
60895
61050
  var import_node_path8 = __toESM(require("node:path"), 1);
60896
- var import_neverthrow34 = __toESM(require_index_cjs(), 1);
61051
+ var import_neverthrow35 = __toESM(require_index_cjs(), 1);
60897
61052
  function resolveLastPath(argument, stateContent) {
60898
61053
  if (argument !== void 0) {
60899
- return (0, import_neverthrow34.ok)(argument);
61054
+ return (0, import_neverthrow35.ok)(argument);
60900
61055
  }
60901
61056
  const trimmed = stateContent?.trim();
60902
61057
  if (trimmed) {
60903
- return (0, import_neverthrow34.ok)(trimmed);
61058
+ return (0, import_neverthrow35.ok)(trimmed);
60904
61059
  }
60905
- return (0, import_neverthrow34.err)({ type: "NO_ARG_AND_NO_STATE" });
61060
+ return (0, import_neverthrow35.err)({ type: "NO_ARG_AND_NO_STATE" });
60906
61061
  }
60907
61062
  function lastPathFilePath(xqaDirectoryectory) {
60908
61063
  return import_node_path8.default.join(xqaDirectoryectory, "last-findings-path");
@@ -60914,7 +61069,7 @@ function writeLastPath(xqaDirectory, findingsPath) {
60914
61069
  // src/shell/instructions.ts
60915
61070
  var import_promises17 = require("node:fs/promises");
60916
61071
  var import_node_path9 = __toESM(require("node:path"), 1);
60917
- var import_neverthrow35 = __toESM(require_index_cjs(), 1);
61072
+ var import_neverthrow36 = __toESM(require_index_cjs(), 1);
60918
61073
  var HTML_COMMENT_PATTERN = /<!--[\s\S]*?-->/g;
60919
61074
  function isEnoentError(value) {
60920
61075
  return value !== null && typeof value === "object" && "code" in value && value.code === "ENOENT";
@@ -60924,9 +61079,9 @@ function toInstructionsError(cause) {
60924
61079
  }
60925
61080
  function absentInstructions() {
60926
61081
  const absent = void 0;
60927
- return (0, import_neverthrow35.ok)(absent);
61082
+ return (0, import_neverthrow36.ok)(absent);
60928
61083
  }
60929
- var safeReadFile2 = import_neverthrow35.ResultAsync.fromThrowable(
61084
+ var safeReadFile2 = import_neverthrow36.ResultAsync.fromThrowable(
60930
61085
  async (filePath) => (0, import_promises17.readFile)(filePath, "utf8"),
60931
61086
  toInstructionsError
60932
61087
  );
@@ -60940,7 +61095,7 @@ function readInstructions(xqaDirectory) {
60940
61095
  if (isEnoentError(error48.cause)) {
60941
61096
  return absentInstructions();
60942
61097
  }
60943
- return (0, import_neverthrow35.err)(error48);
61098
+ return (0, import_neverthrow36.err)(error48);
60944
61099
  });
60945
61100
  }
60946
61101
 
@@ -61029,8 +61184,10 @@ ${cause}
61029
61184
  }
61030
61185
 
61031
61186
  // src/commands/init-command.ts
61187
+ var import_node_child_process5 = require("node:child_process");
61032
61188
  var import_node_fs5 = require("node:fs");
61033
61189
  var import_node_path11 = __toESM(require("node:path"), 1);
61190
+ var import_node_url = require("node:url");
61034
61191
  var GITIGNORE_CONTENT = `/output
61035
61192
  /last-findings-path
61036
61193
  `;
@@ -61050,17 +61207,27 @@ Example: The app starts on the home screen with a wallet already loaded.
61050
61207
  If this file contains a mnemonic phrase, add .xqa/instructions.md to your .gitignore.
61051
61208
  -->
61052
61209
  `;
61210
+ function resolveSkillPath(skillName) {
61211
+ const packageDistributionDirectory = import_node_path11.default.dirname((0, import_node_url.fileURLToPath)(__importMetaUrl));
61212
+ return import_node_path11.default.join(packageDistributionDirectory, "skills", skillName);
61213
+ }
61053
61214
  function runInitCommand() {
61054
61215
  const xqaDirectory = import_node_path11.default.join(process.cwd(), ".xqa");
61216
+ (0, import_node_child_process5.spawnSync)("npx", ["skills", "add", resolveSkillPath("xqa-spec"), "--all", "-y"], {
61217
+ stdio: "inherit"
61218
+ });
61055
61219
  if ((0, import_node_fs5.existsSync)(xqaDirectory)) {
61056
- process.stderr.write(`xqa already initialized: ${xqaDirectory}
61220
+ process.stdout.write(`Skills updated. .xqa already exists, skipping project init.
61057
61221
  `);
61058
- process.exit(1);
61059
61222
  return;
61060
61223
  }
61061
61224
  (0, import_node_fs5.mkdirSync)(xqaDirectory);
61062
61225
  (0, import_node_fs5.writeFileSync)(import_node_path11.default.join(xqaDirectory, ".gitignore"), GITIGNORE_CONTENT);
61063
61226
  (0, import_node_fs5.writeFileSync)(import_node_path11.default.join(xqaDirectory, "instructions.md"), INSTRUCTIONS_TEMPLATE);
61227
+ for (const subdir of ["designs", "specs", "suites"]) {
61228
+ (0, import_node_fs5.mkdirSync)(import_node_path11.default.join(xqaDirectory, subdir));
61229
+ (0, import_node_fs5.writeFileSync)(import_node_path11.default.join(xqaDirectory, subdir, ".gitkeep"), "");
61230
+ }
61064
61231
  process.stdout.write(`Initialized xqa project: ${xqaDirectory}
61065
61232
  `);
61066
61233
  process.stdout.write(`Edit .xqa/instructions.md to describe your app.
@@ -61070,7 +61237,7 @@ function runInitCommand() {
61070
61237
  // src/commands/review-command.ts
61071
61238
  var import_node_fs6 = require("node:fs");
61072
61239
  var import_node_path13 = __toESM(require("node:path"), 1);
61073
- var import_neverthrow37 = __toESM(require_index_cjs(), 1);
61240
+ var import_neverthrow38 = __toESM(require_index_cjs(), 1);
61074
61241
 
61075
61242
  // ../../node_modules/.pnpm/@inquirer+core@10.3.2_@types+node@22.19.15/node_modules/@inquirer/core/dist/esm/lib/key.js
61076
61243
  var isUpKey = (key, keybindings = []) => (
@@ -63475,7 +63642,7 @@ var esm_default11 = createPrompt((config3, done) => {
63475
63642
  });
63476
63643
 
63477
63644
  // src/review-session.ts
63478
- var import_neverthrow36 = __toESM(require_index_cjs(), 1);
63645
+ var import_neverthrow37 = __toESM(require_index_cjs(), 1);
63479
63646
  var CONFIDENCE_PERCENT = 100;
63480
63647
  var FLOW_COL_WIDTH = 35;
63481
63648
  var TRIGGER_COL_WIDTH = 16;
@@ -63628,15 +63795,15 @@ async function runInteractiveLoop(findings, existing) {
63628
63795
  }
63629
63796
  return { staged: state.staged, undoneKeys: state.undoneKeys };
63630
63797
  }
63631
- var safeRunInteractiveLoop = (0, import_neverthrow36.fromAsyncThrowable)(
63798
+ var safeRunInteractiveLoop = (0, import_neverthrow37.fromAsyncThrowable)(
63632
63799
  runInteractiveLoop,
63633
63800
  (error48) => error48 instanceof Error && error48.name === "ExitPromptError" ? "exit-prompt" : "unexpected"
63634
63801
  );
63635
63802
 
63636
63803
  // 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) => {
63804
+ var safeReadFile3 = (0, import_neverthrow38.fromThrowable)((filePath) => (0, import_node_fs6.readFileSync)(filePath, "utf8"));
63805
+ var safeParseJson2 = (0, import_neverthrow38.fromThrowable)(JSON.parse);
63806
+ var safeWrite = (0, import_neverthrow38.fromThrowable)((filePath, content) => {
63640
63807
  (0, import_node_fs6.writeFileSync)(filePath, content);
63641
63808
  });
63642
63809
  function readLastPath(xqaDirectory) {
@@ -63653,13 +63820,13 @@ function isPipelineOutput(data) {
63653
63820
  function readFindings(filePath) {
63654
63821
  const readResult = safeReadFile3(filePath);
63655
63822
  if (readResult.isErr()) {
63656
- return (0, import_neverthrow37.err)("not-found");
63823
+ return (0, import_neverthrow38.err)("not-found");
63657
63824
  }
63658
63825
  return safeParseJson2(readResult.value).mapErr(() => "invalid").andThen((data) => {
63659
63826
  if (!isPipelineOutput(data)) {
63660
- return (0, import_neverthrow37.err)("invalid");
63827
+ return (0, import_neverthrow38.err)("invalid");
63661
63828
  }
63662
- return (0, import_neverthrow37.ok)(data);
63829
+ return (0, import_neverthrow38.ok)(data);
63663
63830
  });
63664
63831
  }
63665
63832
  function loadExistingDismissals(filePath) {
@@ -63732,7 +63899,7 @@ function resolveAndReadFindings(findingsPath, xqaDirectory) {
63732
63899
  "No findings path provided and no last path found. Run: xqa review <findings-path>\n"
63733
63900
  );
63734
63901
  process.exit(1);
63735
- return (0, import_neverthrow37.err)();
63902
+ return (0, import_neverthrow38.err)();
63736
63903
  }
63737
63904
  const resolvedPath = resolvedPathResult.value;
63738
63905
  const findingsResult = readFindings(resolvedPath);
@@ -63745,9 +63912,9 @@ function resolveAndReadFindings(findingsPath, xqaDirectory) {
63745
63912
  `);
63746
63913
  }
63747
63914
  process.exit(1);
63748
- return (0, import_neverthrow37.err)();
63915
+ return (0, import_neverthrow38.err)();
63749
63916
  }
63750
- return (0, import_neverthrow37.ok)({ resolvedPath, output: findingsResult.value });
63917
+ return (0, import_neverthrow38.ok)({ resolvedPath, output: findingsResult.value });
63751
63918
  }
63752
63919
  async function runReviewLoop({
63753
63920
  findings,
@@ -63800,45 +63967,42 @@ async function runReviewCommand(findingsPath, xqaDirectory) {
63800
63967
  // src/commands/spec-command.ts
63801
63968
  var import_node_fs7 = require("node:fs");
63802
63969
  var import_node_path15 = __toESM(require("node:path"), 1);
63803
- var import_neverthrow39 = __toESM(require_index_cjs(), 1);
63970
+ var import_neverthrow40 = __toESM(require_index_cjs(), 1);
63804
63971
 
63805
63972
  // src/spec-frontmatter.ts
63806
- var import_neverthrow38 = __toESM(require_index_cjs(), 1);
63973
+ var import_neverthrow39 = __toESM(require_index_cjs(), 1);
63807
63974
  var FRONTMATTER_OPEN_LEN = 4;
63808
63975
  var FRONTMATTER_MARKER_LEN = 3;
63809
63976
  function extractFrontmatterBlock(content) {
63810
63977
  const normalized = content.replaceAll("\r\n", "\n");
63811
63978
  if (!normalized.startsWith("---")) {
63812
- return (0, import_neverthrow38.err)({ type: "MISSING_FRONTMATTER" });
63979
+ return (0, import_neverthrow39.err)({ type: "MISSING_FRONTMATTER" });
63813
63980
  }
63814
63981
  const end = normalized.indexOf("\n---", FRONTMATTER_MARKER_LEN);
63815
63982
  if (end === -1) {
63816
- return (0, import_neverthrow38.err)({ type: "MISSING_FRONTMATTER" });
63983
+ return (0, import_neverthrow39.err)({ type: "MISSING_FRONTMATTER" });
63817
63984
  }
63818
- return (0, import_neverthrow38.ok)(normalized.slice(FRONTMATTER_OPEN_LEN, end));
63985
+ return (0, import_neverthrow39.ok)(normalized.slice(FRONTMATTER_OPEN_LEN, end));
63819
63986
  }
63820
63987
  function parseMaxSteps(fields) {
63821
63988
  const maxStepsRaw = fields.get("max_steps");
63822
63989
  if (maxStepsRaw === void 0) {
63823
- return (0, import_neverthrow38.ok)(maxStepsRaw);
63990
+ return (0, import_neverthrow39.ok)(maxStepsRaw);
63824
63991
  }
63825
63992
  const parsed = Number(maxStepsRaw);
63826
63993
  if (!Number.isInteger(parsed) || parsed <= 0) {
63827
- return (0, import_neverthrow38.err)({ type: "PARSE_ERROR", cause: `invalid max_steps: ${maxStepsRaw}` });
63994
+ return (0, import_neverthrow39.err)({ type: "PARSE_ERROR", cause: `invalid max_steps: ${maxStepsRaw}` });
63828
63995
  }
63829
- return (0, import_neverthrow38.ok)(parsed);
63996
+ return (0, import_neverthrow39.ok)(parsed);
63830
63997
  }
63831
63998
  function parseSpecFrontmatter(content) {
63832
63999
  return extractFrontmatterBlock(content).andThen((block) => {
63833
64000
  const fields = parseYamlFields(block);
63834
64001
  const feature = fields.get("feature");
63835
64002
  if (feature === void 0) {
63836
- return (0, import_neverthrow38.err)({ type: "MISSING_FIELD", field: "feature" });
64003
+ return (0, import_neverthrow39.err)({ type: "MISSING_FIELD", field: "feature" });
63837
64004
  }
63838
64005
  const entry = fields.get("entry");
63839
- if (entry === void 0) {
63840
- return (0, import_neverthrow38.err)({ type: "MISSING_FIELD", field: "entry" });
63841
- }
63842
64006
  return parseMaxSteps(fields).map((maxSteps) => ({ feature, entry, maxSteps }));
63843
64007
  });
63844
64008
  }
@@ -63877,14 +64041,22 @@ function stripExtensions(filename) {
63877
64041
  }
63878
64042
 
63879
64043
  // src/commands/spec-command.ts
63880
- var safeReadFile4 = (0, import_neverthrow39.fromThrowable)((filePath) => (0, import_node_fs7.readFileSync)(filePath, "utf8"));
64044
+ var safeReadFile4 = (0, import_neverthrow40.fromThrowable)((filePath) => (0, import_node_fs7.readFileSync)(filePath, "utf8"));
64045
+ var safeReaddir = (0, import_neverthrow40.fromThrowable)(
64046
+ (directory) => (0, import_node_fs7.readdirSync)(directory, { recursive: true, encoding: "utf8" })
64047
+ );
64048
+ var CANCEL = "xqa:cancel";
64049
+ var safeSelect = import_neverthrow40.ResultAsync.fromThrowable(
64050
+ esm_default11,
64051
+ (error48) => error48 instanceof Error && error48.name === "ExitPromptError" ? "cancelled" : "failed"
64052
+ );
63881
64053
  function buildSpecExplorer(input, context) {
63882
64054
  return {
63883
64055
  mode: "spec",
63884
64056
  specFiles: [context.absolutePath],
63885
64057
  mcpServers: createDefaultMcpServers(),
63886
64058
  allowedTools: ALLOWED_TOOLS,
63887
- userPrompt: `Navigate to \`${context.entry}\` before beginning spec verification.`,
64059
+ userPrompt: context.entry ? `Navigate to \`${context.entry}\` before beginning spec verification.` : void 0,
63888
64060
  buildEnv: context.config.QA_BUILD_ENV
63889
64061
  };
63890
64062
  }
@@ -63937,33 +64109,66 @@ ${cause}
63937
64109
  `);
63938
64110
  process.exit(1);
63939
64111
  }
64112
+ function findSpecFiles(xqaDirectory) {
64113
+ const specsDirectory = import_node_path15.default.join(xqaDirectory, "specs");
64114
+ return safeReaddir(specsDirectory).unwrapOr([]).filter((file2) => file2.endsWith(".test.md")).map((file2) => import_node_path15.default.join(specsDirectory, file2));
64115
+ }
64116
+ async function promptForSpec(specFiles, xqaDirectory) {
64117
+ const result = await safeSelect({
64118
+ message: "Select a spec",
64119
+ choices: [
64120
+ ...specFiles.map((specFile) => ({
64121
+ name: import_node_path15.default.relative(xqaDirectory, specFile),
64122
+ value: specFile
64123
+ })),
64124
+ new Separator(),
64125
+ { name: "Cancel", value: CANCEL }
64126
+ ]
64127
+ });
64128
+ if (result.isErr() || result.value === CANCEL) {
64129
+ process.exit(0);
64130
+ return void 0;
64131
+ }
64132
+ return result.value;
64133
+ }
64134
+ async function resolveSpecFile(specFile, xqaDirectory) {
64135
+ if (specFile !== void 0) {
64136
+ return specFile;
64137
+ }
64138
+ const specFiles = findSpecFiles(xqaDirectory);
64139
+ if (specFiles.length === 0) {
64140
+ process.stderr.write("No spec files found in .xqa/specs/. Create one with /xqa-spec.\n");
64141
+ process.exit(1);
64142
+ return void 0;
64143
+ }
64144
+ return promptForSpec(specFiles, xqaDirectory);
64145
+ }
64146
+ async function executeSpec(input, context) {
64147
+ const result = await runPipeline2(buildPipelineConfig2(input, context));
64148
+ result.match((output) => {
64149
+ handleSpecSuccess(context.xqaDirectory, output);
64150
+ }, handleSpecError);
64151
+ }
63940
64152
  async function runSpecCommand(input, options) {
63941
64153
  const { config: config3, xqaDirectory } = options;
63942
- const absolutePath = import_node_path15.default.resolve(input.specFile);
64154
+ const resolvedSpecFile = await resolveSpecFile(input.specFile, xqaDirectory);
64155
+ if (resolvedSpecFile === void 0) {
64156
+ return;
64157
+ }
64158
+ const absolutePath = import_node_path15.default.resolve(resolvedSpecFile);
63943
64159
  const frontmatter = readAndParseSpec(absolutePath);
63944
64160
  if (frontmatter === void 0) {
63945
64161
  return;
63946
64162
  }
63947
64163
  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);
64164
+ await executeSpec(input, { config: config3, xqaDirectory, absolutePath, entry: frontmatter.entry, slug });
63960
64165
  }
63961
64166
 
63962
64167
  // src/config.ts
63963
64168
  var import_node_path16 = __toESM(require("node:path"), 1);
63964
- var import_node_url = require("node:url");
64169
+ var import_node_url2 = require("node:url");
63965
64170
  var import_dotenv = __toESM(require_main(), 1);
63966
- var import_neverthrow40 = __toESM(require_index_cjs(), 1);
64171
+ var import_neverthrow41 = __toESM(require_index_cjs(), 1);
63967
64172
 
63968
64173
  // ../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/external.js
63969
64174
  var external_exports2 = {};
@@ -68016,7 +68221,7 @@ var configSchema = external_exports2.object({
68016
68221
  });
68017
68222
 
68018
68223
  // src/config.ts
68019
- var packageDirectory = import_node_path16.default.dirname((0, import_node_url.fileURLToPath)(__importMetaUrl));
68224
+ var packageDirectory = import_node_path16.default.dirname((0, import_node_url2.fileURLToPath)(__importMetaUrl));
68020
68225
  function loadConfig() {
68021
68226
  (0, import_dotenv.config)({ path: import_node_path16.default.resolve(packageDirectory, "..", ".env.local") });
68022
68227
  const result = configSchema.safeParse(process.env);
@@ -68024,15 +68229,15 @@ function loadConfig() {
68024
68229
  const messages = result.error.issues.map(
68025
68230
  (issue2) => ` - ${issue2.path.join(".")}: ${issue2.message}`
68026
68231
  );
68027
- return (0, import_neverthrow40.err)({ type: "INVALID_CONFIG", message: `Configuration error:
68232
+ return (0, import_neverthrow41.err)({ type: "INVALID_CONFIG", message: `Configuration error:
68028
68233
  ${messages.join("\n")}` });
68029
68234
  }
68030
- return (0, import_neverthrow40.ok)(result.data);
68235
+ return (0, import_neverthrow41.ok)(result.data);
68031
68236
  }
68032
68237
 
68033
68238
  // src/pid-lock.ts
68034
68239
  var import_node_fs8 = require("node:fs");
68035
- var import_neverthrow41 = __toESM(require_index_cjs(), 1);
68240
+ var import_neverthrow42 = __toESM(require_index_cjs(), 1);
68036
68241
  var PID_FILE = "/tmp/xqa.pid";
68037
68242
  var SIGINT_EXIT_CODE = 130;
68038
68243
  var SIGTERM_EXIT_CODE = 143;
@@ -68041,7 +68246,7 @@ var HARD_TIMEOUT_MS = 1e4;
68041
68246
  var cleanup = () => {
68042
68247
  (0, import_node_fs8.rmSync)(PID_FILE, { force: true });
68043
68248
  };
68044
- var checkProcessRunning = (0, import_neverthrow41.fromThrowable)(
68249
+ var checkProcessRunning = (0, import_neverthrow42.fromThrowable)(
68045
68250
  (pid) => {
68046
68251
  process.kill(pid, 0);
68047
68252
  return true;
@@ -68107,17 +68312,17 @@ function acquireLock() {
68107
68312
  // src/shell/xqa-directory.ts
68108
68313
  var import_node_fs9 = require("node:fs");
68109
68314
  var import_node_path17 = __toESM(require("node:path"), 1);
68110
- var import_neverthrow42 = __toESM(require_index_cjs(), 1);
68315
+ var import_neverthrow43 = __toESM(require_index_cjs(), 1);
68111
68316
  function findXqaDirectory(startDirectory) {
68112
68317
  let current = startDirectory;
68113
68318
  for (; ; ) {
68114
68319
  const candidate = import_node_path17.default.join(current, ".xqa");
68115
68320
  if ((0, import_node_fs9.existsSync)(candidate)) {
68116
- return (0, import_neverthrow42.ok)(candidate);
68321
+ return (0, import_neverthrow43.ok)(candidate);
68117
68322
  }
68118
68323
  const parent = import_node_path17.default.dirname(current);
68119
68324
  if (parent === current) {
68120
- return (0, import_neverthrow42.err)({ type: "XQA_NOT_INITIALIZED" });
68325
+ return (0, import_neverthrow43.err)({ type: "XQA_NOT_INITIALIZED" });
68121
68326
  }
68122
68327
  current = parent;
68123
68328
  }
@@ -68162,7 +68367,7 @@ program2.command("review").description("Review findings and mark false positives
68162
68367
  const xqaDirectory = resolveXqaDirectory();
68163
68368
  void runReviewCommand(findingsPath, xqaDirectory);
68164
68369
  });
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) => {
68370
+ 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("--verbose", "Log tool call results").action((specFile, options) => {
68166
68371
  const xqaDirectory = resolveXqaDirectory();
68167
68372
  void runSpecCommand(
68168
68373
  { specFile, verbose: options.verbose ?? false, signal: controller.signal },