@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/README.md +150 -96
- package/dist/skills/xqa-spec/AGENTS.md +99 -0
- package/dist/skills/xqa-spec/SKILL.md +125 -0
- package/dist/skills/xqa-spec/metadata.json +5 -0
- package/dist/xqa.cjs +710 -359
- package/package.json +8 -7
package/dist/xqa.cjs
CHANGED
|
@@ -3587,7 +3587,7 @@ var require_index_cjs = __commonJS({
|
|
|
3587
3587
|
}, reject);
|
|
3588
3588
|
}
|
|
3589
3589
|
}
|
|
3590
|
-
var
|
|
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
|
|
3729
|
+
return new ResultAsync9(Promise.resolve(new Ok(value)));
|
|
3730
3730
|
}
|
|
3731
3731
|
function errAsync7(err17) {
|
|
3732
|
-
return new
|
|
3732
|
+
return new ResultAsync9(Promise.resolve(new Err(err17)));
|
|
3733
3733
|
}
|
|
3734
|
-
var fromPromise =
|
|
3735
|
-
var fromSafePromise2 =
|
|
3736
|
-
var fromAsyncThrowable9 =
|
|
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) =>
|
|
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) =>
|
|
3763
|
+
var combineResultAsyncListWithAllErrors = (asyncResultList) => ResultAsync9.fromSafePromise(Promise.all(asyncResultList)).andThen(combineResultListWithAllErrors);
|
|
3764
3764
|
exports2.Result = void 0;
|
|
3765
|
-
(function(
|
|
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
|
-
|
|
3776
|
+
Result3.fromThrowable = fromThrowable11;
|
|
3777
3777
|
function combine(resultList) {
|
|
3778
3778
|
return combineResultList(resultList);
|
|
3779
3779
|
}
|
|
3780
|
-
|
|
3780
|
+
Result3.combine = combine;
|
|
3781
3781
|
function combineWithAllErrors(resultList) {
|
|
3782
3782
|
return combineResultListWithAllErrors(resultList);
|
|
3783
3783
|
}
|
|
3784
|
-
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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,
|
|
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,
|
|
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,
|
|
56448
|
+
return (0, import_neverthrow18.err)(message.errors.join("; "));
|
|
56137
56449
|
}
|
|
56138
|
-
return (0,
|
|
56450
|
+
return (0, import_neverthrow18.ok)(true);
|
|
56139
56451
|
}
|
|
56140
|
-
return (0,
|
|
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,
|
|
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,
|
|
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 =
|
|
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
|
|
56775
|
+
return import_neverthrow17.ResultAsync.fromPromise(messagesPromise, String).andThen((innerResult) => {
|
|
56464
56776
|
cleanup2();
|
|
56465
56777
|
if (innerResult.isErr()) {
|
|
56466
|
-
return (0,
|
|
56778
|
+
return (0, import_neverthrow17.err)(innerResult.error);
|
|
56467
56779
|
}
|
|
56468
|
-
return (0,
|
|
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,
|
|
56784
|
+
return (0, import_neverthrow17.err)(sdkError);
|
|
56473
56785
|
}
|
|
56474
|
-
return (0,
|
|
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,
|
|
56800
|
+
const queryRunnerResult = (0, import_neverthrow17.fromThrowable)(Qs, String)({ prompt: inputQueue, options });
|
|
56489
56801
|
if (queryRunnerResult.isErr()) {
|
|
56490
|
-
return (0,
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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,
|
|
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,
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
59874
|
+
return (0, import_neverthrow23.errAsync)({ type: "CLAUDE_RESPONSE_INVALID", raw: cleaned });
|
|
59673
59875
|
}
|
|
59674
|
-
return (0,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
60111
|
+
return (0, import_neverthrow26.errAsync)({ type: "CLAUDE_RESPONSE_INVALID", raw: text });
|
|
59910
60112
|
}
|
|
59911
|
-
return (0,
|
|
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,
|
|
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,
|
|
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
|
|
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,
|
|
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,
|
|
60460
|
+
return (0, import_neverthrow27.fromAsyncThrowable)(
|
|
60259
60461
|
readAndParseSidecar,
|
|
60260
60462
|
() => ({})
|
|
60261
|
-
)(sidecarPath).orElse(() => (0,
|
|
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,
|
|
60472
|
+
return (0, import_neverthrow27.errAsync)(fsError);
|
|
60271
60473
|
}
|
|
60272
60474
|
function missingBuffer() {
|
|
60273
|
-
return (0,
|
|
60475
|
+
return (0, import_neverthrow27.okAsync)(void 0);
|
|
60274
60476
|
}
|
|
60275
60477
|
function missingArtboard() {
|
|
60276
|
-
return (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,
|
|
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,
|
|
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,
|
|
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,
|
|
60528
|
+
return (0, import_neverthrow30.errAsync)(error48);
|
|
60327
60529
|
}
|
|
60328
60530
|
const delay = config3.baseDelayMs * Math.pow(2, attempt - 1);
|
|
60329
|
-
return
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
60430
|
-
var safeParseJson = (0,
|
|
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,
|
|
60644
|
+
return (0, import_neverthrow31.ok)([]);
|
|
60443
60645
|
}
|
|
60444
|
-
return (0,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
60752
|
+
return (0, import_neverthrow32.err)(explorerResult.error);
|
|
60551
60753
|
}
|
|
60552
|
-
return (0,
|
|
60754
|
+
return (0, import_neverthrow32.ok)({ artifacts: explorerResult.value, inspectorFindings });
|
|
60553
60755
|
}
|
|
60554
60756
|
function runExplorerAndDrain(options) {
|
|
60555
|
-
return new
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
60806
|
+
return (0, import_neverthrow28.err)(dismissalsResult.error);
|
|
60605
60807
|
}
|
|
60606
|
-
return (0,
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
|
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,
|
|
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
|
|
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,
|
|
61037
|
+
return (0, import_neverthrow34.err)({ type: "UNSUPPORTED_SHELL", shell });
|
|
60836
61038
|
}
|
|
60837
|
-
return (0,
|
|
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
|
|
61098
|
+
var import_neverthrow35 = __toESM(require_index_cjs(), 1);
|
|
60897
61099
|
function resolveLastPath(argument, stateContent) {
|
|
60898
61100
|
if (argument !== void 0) {
|
|
60899
|
-
return (0,
|
|
61101
|
+
return (0, import_neverthrow35.ok)(argument);
|
|
60900
61102
|
}
|
|
60901
61103
|
const trimmed = stateContent?.trim();
|
|
60902
61104
|
if (trimmed) {
|
|
60903
|
-
return (0,
|
|
61105
|
+
return (0, import_neverthrow35.ok)(trimmed);
|
|
60904
61106
|
}
|
|
60905
|
-
return (0,
|
|
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/
|
|
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
|
|
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
|
|
61124
|
+
function toAppContextError(cause) {
|
|
60923
61125
|
return { type: "READ_FAILED", cause };
|
|
60924
61126
|
}
|
|
60925
|
-
function
|
|
61127
|
+
function absentContext() {
|
|
60926
61128
|
const absent = void 0;
|
|
60927
|
-
return (0,
|
|
61129
|
+
return (0, import_neverthrow36.ok)(absent);
|
|
60928
61130
|
}
|
|
60929
|
-
var safeReadFile2 =
|
|
61131
|
+
var safeReadFile2 = import_neverthrow36.ResultAsync.fromThrowable(
|
|
60930
61132
|
async (filePath) => (0, import_promises17.readFile)(filePath, "utf8"),
|
|
60931
|
-
|
|
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
|
|
60938
|
-
const filePath = import_node_path9.default.join(xqaDirectory,
|
|
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
|
|
61143
|
+
return absentContext();
|
|
60942
61144
|
}
|
|
60943
|
-
return (0,
|
|
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
|
-
|
|
61159
|
+
appContext,
|
|
61160
|
+
initialState
|
|
60952
61161
|
}) {
|
|
60953
|
-
const parts = [
|
|
60954
|
-
const
|
|
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
|
-
|
|
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:
|
|
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
|
|
61015
|
-
(
|
|
61016
|
-
|
|
61017
|
-
|
|
61018
|
-
|
|
61019
|
-
|
|
61020
|
-
|
|
61021
|
-
|
|
61022
|
-
|
|
61023
|
-
|
|
61024
|
-
|
|
61025
|
-
|
|
61026
|
-
|
|
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
|
|
61038
|
-
|
|
61039
|
-
Example:
|
|
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
|
-
<!--
|
|
61043
|
-
|
|
61044
|
-
|
|
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
|
-
<!--
|
|
61048
|
-
|
|
61049
|
-
|
|
61050
|
-
|
|
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.
|
|
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, "
|
|
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(
|
|
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
|
|
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
|
|
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,
|
|
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,
|
|
63638
|
-
var safeParseJson2 = (0,
|
|
63639
|
-
var safeWrite = (0,
|
|
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,
|
|
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,
|
|
63915
|
+
return (0, import_neverthrow38.err)("invalid");
|
|
63661
63916
|
}
|
|
63662
|
-
return (0,
|
|
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,
|
|
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,
|
|
64003
|
+
return (0, import_neverthrow38.err)();
|
|
63749
64004
|
}
|
|
63750
|
-
return (0,
|
|
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
|
|
64058
|
+
var import_neverthrow40 = __toESM(require_index_cjs(), 1);
|
|
63804
64059
|
|
|
63805
64060
|
// src/spec-frontmatter.ts
|
|
63806
|
-
var
|
|
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,
|
|
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,
|
|
64071
|
+
return (0, import_neverthrow39.err)({ type: "MISSING_FRONTMATTER" });
|
|
63817
64072
|
}
|
|
63818
|
-
return (0,
|
|
64073
|
+
return (0, import_neverthrow39.ok)(normalized.slice(FRONTMATTER_OPEN_LEN, end));
|
|
63819
64074
|
}
|
|
63820
|
-
function
|
|
63821
|
-
const
|
|
63822
|
-
if (
|
|
63823
|
-
return (0,
|
|
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(
|
|
63826
|
-
if (
|
|
63827
|
-
return (0,
|
|
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,
|
|
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,
|
|
64091
|
+
return (0, import_neverthrow39.err)({ type: "MISSING_FIELD", field: "feature" });
|
|
63837
64092
|
}
|
|
63838
64093
|
const entry = fields.get("entry");
|
|
63839
|
-
|
|
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,
|
|
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
|
-
|
|
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:
|
|
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
|
|
63942
|
-
|
|
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
|
|
63948
|
-
|
|
63949
|
-
|
|
63950
|
-
|
|
63951
|
-
|
|
63952
|
-
|
|
63953
|
-
|
|
63954
|
-
|
|
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
|
|
64288
|
+
var import_node_url2 = require("node:url");
|
|
63965
64289
|
var import_dotenv = __toESM(require_main(), 1);
|
|
63966
|
-
var
|
|
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,
|
|
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,
|
|
68351
|
+
return (0, import_neverthrow41.err)({ type: "INVALID_CONFIG", message: `Configuration error:
|
|
68028
68352
|
${messages.join("\n")}` });
|
|
68029
68353
|
}
|
|
68030
|
-
return (0,
|
|
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
|
|
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,
|
|
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
|
|
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,
|
|
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,
|
|
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(
|
|
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("
|
|
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
|
|
68519
|
+
{ specFile, verbose: options.verbose, signal: controller.signal },
|
|
68169
68520
|
{ config: config2, xqaDirectory }
|
|
68170
68521
|
);
|
|
68171
68522
|
});
|