@validate.qa/runner 1.0.48 → 1.0.49

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/dist/cli.js +262 -34
  2. package/dist/cli.mjs +262 -34
  3. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -77288,7 +77288,7 @@ var require_discover_ai_planner = __commonJS({
77288
77288
  "../runner-core/dist/services/discover-ai-planner.js"(exports2) {
77289
77289
  "use strict";
77290
77290
  Object.defineProperty(exports2, "__esModule", { value: true });
77291
- exports2.PLANNER_STRENGTHEN_GATE_PROMPT = exports2.SCENARIO_SYSTEM_PROMPT = void 0;
77291
+ exports2.PLANNER_STRENGTHEN_GATE_PROMPT = exports2.SCENARIO_SYSTEM_PROMPT = exports2.SURVEY_SYSTEM_PROMPT = void 0;
77292
77292
  exports2.getAIPlannerMaxTokens = getAIPlannerMaxTokens;
77293
77293
  exports2.getSnapshotConfidence = getSnapshotConfidence;
77294
77294
  exports2.compressSiteMap = compressSiteMap;
@@ -77824,7 +77824,7 @@ Plan these LOGGED-OUT instead (keep their own establishment steps; do NOT assume
77824
77824
  }
77825
77825
  return sections.join("\n\n");
77826
77826
  }
77827
- var SURVEY_SYSTEM_PROMPT = `You are a feature analyst for a web application. You receive evidence from an AI explorer that mapped the entire site. Your job is to organize pages into meaningful feature groups.
77827
+ exports2.SURVEY_SYSTEM_PROMPT = `You are a feature analyst for a web application. You receive evidence from an AI explorer that mapped the entire site. Your job is to organize pages into meaningful feature groups.
77828
77828
 
77829
77829
  ## Output Discipline
77830
77830
  Return ONLY one valid JSON object matching the schema below. Do NOT include <think> blocks, markdown fences, analysis, prose, or any text outside the JSON object. Put any brief explanation only in the JSON "reasoning" field.
@@ -77844,6 +77844,7 @@ Return ONLY one valid JSON object matching the schema below. Do NOT include <thi
77844
77844
  6. Do NOT merge pages into one feature just because they serve a similar broader goal. Treat areas as separate features when the evidence shows separate primary navigation destinations (e.g. separate sidebar links), separate route families below the app shell (e.g. /dashboard/documents/* vs /dashboard/reports/* are separate even though both are under /dashboard), or different CRUD entities/forms. Keep pages together when they share one navigational area and operate on the same entity (e.g. /applications, /applications/stats, /applications?filter= all belong together).
77845
77845
  7. Feature descriptions MUST reference actual element labels from the evidence
77846
77846
  8. Do NOT create standalone features for generic error pages (404, 500, access denied). If a business route is broken or crashes, keep it in its intended feature and note the failure in the description. Error pages do not need their own deep exploration run.
77847
+ 9. The Base URL's PATH is the survey's entry scope. When it is non-root (e.g. \`/shop/checkout\`), the operator is auditing that one flow in isolation: do NOT create featureGroups for parent paths, sibling paths, or guessed/nonexistent routes on the same host \u2014 even if the explorer's evidence wandered there. Auth pages the flow itself traverses (its login page, an \`/api/auth/*\` callback) belong INSIDE that flow's group, not in separate groups. When the Base URL path is \`/\`, group the whole app as usual.
77847
77848
 
77848
77849
  ## Output JSON Schema
77849
77850
  {
@@ -78596,7 +78597,7 @@ Keep the exact JSON schema { "featureGroups":[...], "scenarios":[...], "reasonin
78596
78597
  const agentModel = options.modelOverride ? (0, ai_provider_js_1.resolveModelForTask)(options.modelOverride, "agent") : (0, ai_provider_js_1.getModel)("agent");
78597
78598
  const plannerMaxTokens = getAIPlannerMaxTokens(agentModel);
78598
78599
  logs2.push(`AI planner [${context.mode}]: ${siteMap.pages.length} pages, ${evidence.recordedJourneys.length} journeys, budget ${context.testBudget}`);
78599
- const systemPrompt = options.systemPromptOverride ?? (context.mode === "survey" ? SURVEY_SYSTEM_PROMPT : exports2.SCENARIO_SYSTEM_PROMPT);
78600
+ const systemPrompt = options.systemPromptOverride ?? (context.mode === "survey" ? exports2.SURVEY_SYSTEM_PROMPT : exports2.SCENARIO_SYSTEM_PROMPT);
78600
78601
  const userPrompt = options.userPromptOverride ?? formatEvidenceForPlanner(compressed, evidence, context);
78601
78602
  logs2.push(`AI planner: ~${Math.ceil(userPrompt.length / 3.5)} input tokens`);
78602
78603
  const aiClient = options.aiClient ?? (options.modelOverride ? (0, ai_provider_js_1.getRunnerClientForModel)(options.modelOverride) : (0, ai_provider_js_1.getRunnerAIClient)());
@@ -321567,6 +321568,168 @@ var require_evidence_harvester = __commonJS({
321567
321568
  }
321568
321569
  });
321569
321570
 
321571
+ // ../runner-core/dist/services/survey-scope.js
321572
+ var require_survey_scope = __commonJS({
321573
+ "../runner-core/dist/services/survey-scope.js"(exports2) {
321574
+ "use strict";
321575
+ Object.defineProperty(exports2, "__esModule", { value: true });
321576
+ exports2.deriveSurveyPathScope = deriveSurveyPathScope;
321577
+ exports2.isAuthFlowRoute = isAuthFlowRoute;
321578
+ exports2.buildSurveyScopeBoundary = buildSurveyScopeBoundary;
321579
+ exports2.filterFeatureGroupsToPathScope = filterFeatureGroupsToPathScope;
321580
+ var exploration_parser_js_1 = require_exploration_parser();
321581
+ function deriveSurveyPathScope(baseUrl) {
321582
+ const trimmed = (baseUrl ?? "").trim();
321583
+ if (!trimmed)
321584
+ return null;
321585
+ let parsed;
321586
+ try {
321587
+ parsed = new URL(trimmed);
321588
+ } catch {
321589
+ return null;
321590
+ }
321591
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:")
321592
+ return null;
321593
+ const hashRoute = parsed.hash.startsWith("#/") ? parsed.hash : "";
321594
+ const basePath = (0, exploration_parser_js_1.normalizeRouteForFocusScope)(`${parsed.pathname}${hashRoute}`);
321595
+ if (basePath === "/" || basePath === "/#")
321596
+ return null;
321597
+ return { origin: parsed.origin, basePath };
321598
+ }
321599
+ var AUTH_FLOW_SEGMENTS = /* @__PURE__ */ new Set([
321600
+ "auth",
321601
+ "oauth",
321602
+ "oauth2",
321603
+ "callback",
321604
+ "login",
321605
+ "log-in",
321606
+ "signin",
321607
+ "sign-in",
321608
+ "signup",
321609
+ "sign-up",
321610
+ "register",
321611
+ "logout",
321612
+ "log-out",
321613
+ "signout",
321614
+ "sign-out",
321615
+ "sso",
321616
+ "saml",
321617
+ "oidc"
321618
+ ]);
321619
+ function isAuthFlowRoute(route2) {
321620
+ const normalized = (0, exploration_parser_js_1.normalizeRouteForFocusScope)(route2).toLowerCase();
321621
+ return normalized.split(/[/#]/).some((segment) => AUTH_FLOW_SEGMENTS.has(segment));
321622
+ }
321623
+ function routePathForScope(route2, origin) {
321624
+ const trimmed = (route2 ?? "").trim();
321625
+ if (trimmed.startsWith("//")) {
321626
+ try {
321627
+ const parsed = new URL(`https:${trimmed}`);
321628
+ if (parsed.host !== new URL(origin).host)
321629
+ return null;
321630
+ return `${parsed.pathname}${parsed.search}${parsed.hash}`;
321631
+ } catch {
321632
+ return null;
321633
+ }
321634
+ }
321635
+ if (/^https?:\/\//i.test(trimmed)) {
321636
+ try {
321637
+ const parsed = new URL(trimmed);
321638
+ if (parsed.origin !== origin)
321639
+ return null;
321640
+ return `${parsed.pathname}${parsed.search}${parsed.hash}`;
321641
+ } catch {
321642
+ return trimmed;
321643
+ }
321644
+ }
321645
+ if (trimmed.startsWith("/") && trimmed.includes("..")) {
321646
+ try {
321647
+ const resolved = new URL(trimmed, "https://scope.invalid");
321648
+ return `${resolved.pathname}${resolved.search}${resolved.hash}`;
321649
+ } catch {
321650
+ return trimmed;
321651
+ }
321652
+ }
321653
+ return trimmed;
321654
+ }
321655
+ function buildSurveyScopeBoundary(baseUrl) {
321656
+ const scope = deriveSurveyPathScope(baseUrl);
321657
+ const lines = [
321658
+ '- **Never invent a URL.** Navigate only to pages the app presented (links/buttons you can click, per the rule above) or redirects it performs. Do not guess "common" routes and do not probe made-up routes to see what the error page does'
321659
+ ];
321660
+ if (scope) {
321661
+ lines.push(`- **The base URL PATH is the entry scope** (\`${scope.basePath}\`) \u2014 this survey audits that flow in ISOLATION. Do not truncate the path to visit a parent, and do not visit or catalog sibling paths on this host that in-scope pages never linked to. Pages the flow itself pushes you through (its login page, an auth/OAuth callback, a post-submit redirect) are in scope`);
321662
+ }
321663
+ return lines.join("\n");
321664
+ }
321665
+ function filterFeatureGroupsToPathScope(featureGroups, baseUrl) {
321666
+ const scope = deriveSurveyPathScope(baseUrl);
321667
+ if (!scope) {
321668
+ return { kept: featureGroups, droppedGroups: [], trimmedRoutes: [], trimmedPages: [], basePath: null };
321669
+ }
321670
+ const withinBase = (route2) => {
321671
+ const path = routePathForScope(route2, scope.origin);
321672
+ if (path === null)
321673
+ return false;
321674
+ if ((0, exploration_parser_js_1.routeIsWithinFocusAreas)(path, [scope.basePath]))
321675
+ return true;
321676
+ const norm = (0, exploration_parser_js_1.normalizeRouteForFocusScope)(path);
321677
+ return norm === `${scope.basePath}#` || norm.startsWith(`${scope.basePath}#/`);
321678
+ };
321679
+ const allowedAuthFlow = (route2) => {
321680
+ const path = routePathForScope(route2, scope.origin);
321681
+ return path !== null && isAuthFlowRoute(path);
321682
+ };
321683
+ const kept = [];
321684
+ const droppedGroups = [];
321685
+ const trimmedRoutes = [];
321686
+ const trimmedPages = [];
321687
+ for (const group of featureGroups) {
321688
+ const routes = Array.isArray(group.routes) ? group.routes : [];
321689
+ const keptRoutes = [];
321690
+ const removedRoutes = [];
321691
+ let anchored = false;
321692
+ for (const route2 of routes) {
321693
+ if (withinBase(route2)) {
321694
+ anchored = true;
321695
+ keptRoutes.push(route2);
321696
+ } else if (allowedAuthFlow(route2)) {
321697
+ keptRoutes.push(route2);
321698
+ } else {
321699
+ removedRoutes.push(route2);
321700
+ }
321701
+ }
321702
+ if (!anchored) {
321703
+ droppedGroups.push({ name: group.name, routes: [...routes] });
321704
+ continue;
321705
+ }
321706
+ let pages = group.pages;
321707
+ const removedPages = [];
321708
+ if (Array.isArray(group.pages)) {
321709
+ const keptPages = [];
321710
+ for (const page of group.pages) {
321711
+ if (withinBase(page.route) || allowedAuthFlow(page.route))
321712
+ keptPages.push(page);
321713
+ else
321714
+ removedPages.push(page.route);
321715
+ }
321716
+ pages = keptPages;
321717
+ }
321718
+ if (removedRoutes.length === 0 && removedPages.length === 0) {
321719
+ kept.push(group);
321720
+ } else {
321721
+ kept.push({ ...group, routes: keptRoutes, ...group.pages !== void 0 ? { pages } : {} });
321722
+ if (removedRoutes.length > 0)
321723
+ trimmedRoutes.push({ name: group.name, routes: removedRoutes });
321724
+ if (removedPages.length > 0)
321725
+ trimmedPages.push({ name: group.name, routes: removedPages });
321726
+ }
321727
+ }
321728
+ return { kept, droppedGroups, trimmedRoutes, trimmedPages, basePath: scope.basePath };
321729
+ }
321730
+ }
321731
+ });
321732
+
321570
321733
  // ../runner-core/dist/services/discover-explorer.js
321571
321734
  var require_discover_explorer = __commonJS({
321572
321735
  "../runner-core/dist/services/discover-explorer.js"(exports2) {
@@ -321608,6 +321771,7 @@ var require_discover_explorer = __commonJS({
321608
321771
  var evidence_harvester_js_1 = require_evidence_harvester();
321609
321772
  var exploration_parser_js_1 = require_exploration_parser();
321610
321773
  var survey_link_frontier_js_1 = require_survey_link_frontier();
321774
+ var survey_scope_js_1 = require_survey_scope();
321611
321775
  var snapshot_context_compactor_js_2 = require_snapshot_context_compactor();
321612
321776
  Object.defineProperty(exports2, "formatMCPToolResult", { enumerable: true, get: function() {
321613
321777
  return snapshot_context_compactor_js_2.formatMCPToolResult;
@@ -322035,6 +322199,7 @@ If a snapshot's readiness section exposes \`loading_indicators\`, \`skeleton_con
322035
322199
  - **Map, do not mutate.** You MAY click buttons that open modals, popups, or dropdowns for the purpose of CATALOGING their contents (capture_page). Do NOT fill forms, submit data, or click destructive buttons (save, delete, submit) - just catalog the elements and close the modal
322036
322200
  - **Modals/popups/banners**: If a cookie consent banner, onboarding tour, or overlay appears, dismiss it (click close/X/dismiss/accept) and continue. If it reappears on the next page, dismiss it again - some sites show consent banners on every navigation until accepted
322037
322201
  - **Stay on the same origin** as the base URL. Note external links in elements but do not follow them
322202
+ ${(0, survey_scope_js_1.buildSurveyScopeBoundary)(baseUrl)}
322038
322203
  - **OAuth/SSO**: If login redirects to an external provider (Google, GitHub), do not follow it. Note that the app uses OAuth and explore unauthenticated pages only
322039
322204
  - **Auth failure**: If login${isRegisterMode ? " or account registration" : ""} fails after 2 attempts, capture the auth page and explore unauthenticated pages. Note the failure in your \`finish_exploration\` summary
322040
322205
  - **Parameterized routes**: /users/1 and /users/2 are the same page template. Visit ONE instance, not all of them
@@ -325533,6 +325698,8 @@ var require_agentic_survey_prompts = __commonJS({
325533
325698
  exports2.validateSurveyorOutput = validateSurveyorOutput;
325534
325699
  exports2.buildSurveyAgentsMd = buildSurveyAgentsMd;
325535
325700
  exports2.buildSurveyBriefMd = buildSurveyBriefMd;
325701
+ exports2.buildClaudeSurveyKickoffPrompt = buildClaudeSurveyKickoffPrompt;
325702
+ var survey_scope_js_1 = require_survey_scope();
325536
325703
  var CRITICALITY_VALUES = ["critical", "high", "medium", "low"];
325537
325704
  function isString(v) {
325538
325705
  return typeof v === "string";
@@ -325712,9 +325879,40 @@ var require_agentic_survey_prompts = __commonJS({
325712
325879
  '- Click "delete" or destructive admin actions.',
325713
325880
  "- Leave the baseUrl domain except briefly for OAuth providers.",
325714
325881
  "",
325882
+ "## Navigation scope",
325883
+ "",
325884
+ "The Base URL in BRIEF.md is the ENTRY SCOPE \u2014 its full path, not just",
325885
+ "its domain. Open the exact Base URL first. From there, navigate ONLY to:",
325886
+ "",
325887
+ "- URLs the app actually presented to you: a link, button, or form",
325888
+ " visible in a snapshot of an in-scope page. (`browser_navigate` to a",
325889
+ " URL you saw as a link href is fine \u2014 composing one yourself is not.)",
325890
+ "- Redirects the app itself performs during those interactions,",
325891
+ " including login/OAuth/SSO callback routes needed to complete a flow.",
325892
+ "- The Base URL itself, to return to the start of the flow.",
325893
+ "",
325894
+ "NEVER invent a URL. Do NOT:",
325895
+ "- Parent-walk: truncating the Base URL path (base `/shop/checkout` \u2192",
325896
+ " visiting `/shop` or `/`) is out of scope even though it is same-origin.",
325897
+ "- Sibling-walk: do not visit or catalog neighbours of the Base URL path",
325898
+ " that in-scope pages never linked to.",
325899
+ '- Guess "common" routes (`/admin`, `/about`) or probe made-up routes',
325900
+ " (`/nonexistent`) to see what the error handling does. Do not create",
325901
+ " featureGroups for generic error/404 pages unless the app itself",
325902
+ " routes users there as part of a real flow.",
325903
+ "- Any other URL surgery: if the UI did not present it and the app did",
325904
+ " not redirect you to it, it does not exist for this survey.",
325905
+ "",
325906
+ "When the Base URL path is non-root (e.g. `/shop/checkout`, `/portal/billing`),",
325907
+ "the operator is auditing THAT flow in isolation: every featureGroup must",
325908
+ "describe that flow. Auth pages the flow pushes you through belong inside",
325909
+ "that flow's featureGroup \u2014 do not mint separate featureGroups for them.",
325910
+ "When the Base URL is the origin root, survey the whole app \u2014 the",
325911
+ "no-guessing rule still applies: reach pages through the UI.",
325912
+ "",
325715
325913
  "## Exploration philosophy",
325716
325914
  "",
325717
- "- Breadth before depth \u2014 map the whole surface before drilling in.",
325915
+ "- Breadth before depth \u2014 map the whole in-scope surface before drilling in.",
325718
325916
  "- Authenticated and unauthenticated surfaces are different products;",
325719
325917
  " explore both when credentials are available.",
325720
325918
  "- Prefer observing over interacting: open a page, snapshot, note the",
@@ -325809,6 +326007,17 @@ var require_agentic_survey_prompts = __commonJS({
325809
326007
  lines.push("");
325810
326008
  lines.push(`**Base URL:** ${input.baseUrl}`);
325811
326009
  lines.push("");
326010
+ const pathScope = (0, survey_scope_js_1.deriveSurveyPathScope)(input.baseUrl);
326011
+ if (pathScope) {
326012
+ lines.push(`**Entry scope:** \`${pathScope.basePath}\` \u2014 this is a SCOPED survey of one flow.`);
326013
+ lines.push("");
326014
+ lines.push(`Everything you catalog must live under \`${pathScope.basePath}\`, or be a page`);
326015
+ lines.push("this flow itself pushes you through (its own login page, an auth/OAuth");
326016
+ lines.push("callback, a post-submit redirect). Parent and sibling paths on this host");
326017
+ lines.push("are OTHER fixtures/apps and are out of scope \u2014 do not visit them, do not");
326018
+ lines.push("catalog them, do not mint featureGroups for them.");
326019
+ lines.push("");
326020
+ }
325812
326021
  if (input.appBrief && input.appBrief.trim().length > 0) {
325813
326022
  lines.push("## App brief from the operator");
325814
326023
  lines.push("");
@@ -325878,6 +326087,37 @@ var require_agentic_survey_prompts = __commonJS({
325878
326087
  lines.push("");
325879
326088
  return lines.join("\n");
325880
326089
  }
326090
+ function buildClaudeSurveyKickoffPrompt(resultFilePath) {
326091
+ return [
326092
+ "Read AGENTS.md, then read BRIEF.md.",
326093
+ "",
326094
+ `Your deliverable is a SurveyorOutput JSON file at ${resultFilePath}.`,
326095
+ "Follow this procedure exactly:",
326096
+ "",
326097
+ "STEP 1 \u2014 Orient: navigate to the EXACT Base URL from BRIEF.md and",
326098
+ " snapshot it, then open 2-3 pages its UI visibly links to (e.g. a login",
326099
+ " link it presents). Navigate only to URLs the page actually presented \u2014",
326100
+ ' never guessed parent, sibling, or "common" routes (see "Navigation',
326101
+ ' scope" in AGENTS.md).',
326102
+ `STEP 2 \u2014 IMMEDIATELY use the Write tool to save a first COMPLETE draft of`,
326103
+ ` the SurveyorOutput JSON to ${resultFilePath}, based on what you have seen`,
326104
+ " so far. featureGroups must be a non-empty array. Do NOT skip this step",
326105
+ " and do NOT explore further until this file exists.",
326106
+ "STEP 3 \u2014 Continue exploring breadth-first. Visit each remaining page AT",
326107
+ " MOST ONCE \u2014 never re-navigate to or re-snapshot a page already seen.",
326108
+ ` After every 3-4 new pages, OVERWRITE ${resultFilePath} with an updated,`,
326109
+ " complete JSON object.",
326110
+ "STEP 4 \u2014 Stop once you have covered the main in-scope pages (around 20",
326111
+ ` page visits is plenty) or stop finding new features. Do a final OVERWRITE`,
326112
+ ` of ${resultFilePath} with the complete SurveyorOutput, then exit.`,
326113
+ "",
326114
+ "Budget: about 40 tool calls total. If you ever sense you are running low,",
326115
+ `immediately overwrite ${resultFilePath} with your best current JSON and`,
326116
+ "exit \u2014 a complete-but-shallow file always beats no file. Never write an",
326117
+ "empty featureGroups array and never omit the key. No markdown fences, no",
326118
+ "prose in the file \u2014 a single JSON object only."
326119
+ ].join("\n");
326120
+ }
325881
326121
  }
325882
326122
  });
325883
326123
 
@@ -326428,6 +326668,9 @@ ${(0, inbox_tools_js_1.buildInboxAgentsGuidance)(inboxReg.email, inboxReg.passwo
326428
326668
  const kickoffPrompt = [
326429
326669
  "Read AGENTS.md, then read BRIEF.md.",
326430
326670
  "Use the playwright MCP tools to explore the site.",
326671
+ 'Follow the "Navigation scope" rules in AGENTS.md: start at the exact Base',
326672
+ 'URL and never navigate to guessed parent, sibling, or "common" routes \u2014',
326673
+ "only URLs the pages actually present (or redirects the app performs).",
326431
326674
  `When done, write your JSON output to ${resultFilePath} as a single JSON object matching the SurveyorOutput schema in AGENTS.md.`,
326432
326675
  "Then exit."
326433
326676
  ].join("\n");
@@ -330144,32 +330387,7 @@ ${(0, inbox_tools_js_1.buildInboxAgentsGuidance)(inboxReg.email, inboxReg.passwo
330144
330387
  await (0, promises_1.writeFile)((0, node_path_1.join)(runDir, "AGENTS.md"), surveyAgentsMd, "utf8");
330145
330388
  await (0, promises_1.writeFile)((0, node_path_1.join)(runDir, "BRIEF.md"), (0, agentic_survey_prompts_js_1.buildSurveyBriefMd)(briefInput), "utf8");
330146
330389
  log2("Wrote AGENTS.md + BRIEF.md");
330147
- const kickoffPrompt = [
330148
- "Read AGENTS.md, then read BRIEF.md.",
330149
- "",
330150
- `Your deliverable is a SurveyorOutput JSON file at ${resultFilePath}.`,
330151
- "Follow this procedure exactly:",
330152
- "",
330153
- "STEP 1 \u2014 Orient: navigate to the base URL and 2-3 obvious pages (e.g. the",
330154
- " login page and the main dashboard).",
330155
- `STEP 2 \u2014 IMMEDIATELY use the Write tool to save a first COMPLETE draft of`,
330156
- ` the SurveyorOutput JSON to ${resultFilePath}, based on what you have seen`,
330157
- " so far. featureGroups must be a non-empty array. Do NOT skip this step",
330158
- " and do NOT explore further until this file exists.",
330159
- "STEP 3 \u2014 Continue exploring breadth-first. Visit each remaining page AT",
330160
- " MOST ONCE \u2014 never re-navigate to or re-snapshot a page already seen.",
330161
- ` After every 3-4 new pages, OVERWRITE ${resultFilePath} with an updated,`,
330162
- " complete JSON object.",
330163
- "STEP 4 \u2014 Stop once you have covered the main pages (around 20 page visits",
330164
- ` is plenty) or stop finding new features. Do a final OVERWRITE of`,
330165
- ` ${resultFilePath} with the complete SurveyorOutput, then exit.`,
330166
- "",
330167
- "Budget: about 40 tool calls total. If you ever sense you are running low,",
330168
- `immediately overwrite ${resultFilePath} with your best current JSON and`,
330169
- "exit \u2014 a complete-but-shallow file always beats no file. Never write an",
330170
- "empty featureGroups array and never omit the key. No markdown fences, no",
330171
- "prose in the file \u2014 a single JSON object only."
330172
- ].join("\n");
330390
+ const kickoffPrompt = (0, agentic_survey_prompts_js_1.buildClaudeSurveyKickoffPrompt)(resultFilePath);
330173
330391
  const credentialScrubMap = {};
330174
330392
  for (const c of credentials) {
330175
330393
  credentialScrubMap[c.email] = "<email>";
@@ -346786,9 +347004,9 @@ var require_dist2 = __commonJS({
346786
347004
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports3, p)) __createBinding(exports3, m, p);
346787
347005
  };
346788
347006
  Object.defineProperty(exports2, "__esModule", { value: true });
346789
- exports2.extractZipSafely = exports2.downloadArchiveCapped = exports2.detectArchiveKind = exports2.acquireRepoForImport = exports2.runFrameworkImportOnRunner = exports2.getAgenticEngine = exports2.resolveRefFromSnapshot = exports2.replayAuthRecipeIntoBrowser = exports2.runRegisterCaptureRun = exports2.runLoginCaptureRun = exports2.filterLoginCaptureCredentials = exports2.buildLoginCaptureCredentials = exports2.isReservedRunnerEnvVar = exports2.RESERVED_RUNNER_ENV_VARS = exports2.AUTH_MARKER_VERSION = exports2.buildHealAuthStamp = exports2.extractAuthRegion = exports2.upsertAuthBlock = exports2.buildAuthRecipe = exports2.commandToReplayStep = exports2.buildReplayPlan = exports2.absoluteRoute = exports2.renderReplaySettleArrow = exports2.renderReplayClickArrow = exports2.renderAuthFn = exports2.canonicalizeRoute = exports2.executeGrokPerBatchHeal = exports2.executeGrokPerTestHeal = exports2.headerVal = exports2.classifyAuthType = exports2.isTextExcerptable = exports2.isStaticAssetByExt = exports2.isApiCall = exports2.buildApiCallSummary = exports2.computeMobileHealOutcome = exports2.detectMobileStepSecretLeak = exports2.redactMobileSecretsFromText = exports2.MOBILE_LIVE_HEAL_SYSTEM_PROMPT = exports2.MOBILE_SCRIPT_HEAL_SYSTEM_PROMPT = exports2.executeMobileLiveHeal = exports2.executeMobileTestWithHealing = exports2.executeMobileScriptDrivenHeal = exports2.prepareMobileScenarioAppState = exports2.runMobileGeneratePhase = exports2.runMobileAIPlanner = exports2.buildMobileSiteMap = exports2.formatMobileEvidenceForPlanner = exports2.MOBILE_SCENARIO_SYSTEM_PROMPT = exports2.MOBILE_SURVEY_SYSTEM_PROMPT = exports2.runMobileExplorePhase = void 0;
346790
- exports2.decideAppRenderType = exports2.detectCanvasApp = exports2.detectRateLimitBlock = exports2.isSafeCanonicalRedirect = exports2.resolveBaseUrlPreflight = exports2.checkBaseUrlRedirect = exports2.stripJsonFences = exports2.scrubCredentials = exports2.withAIRetry = exports2.snapshotMatchingChildren = exports2.findNewChromiumChildPid = exports2.killProcessTree = exports2.gracefulCloseWithTreeKill = exports2.liveBrowserRegistry = exports2.REPO_SWITCH_REMOVED_FRACTION = exports2.MAX_MANIFEST_FILES = exports2.toDiffSummary = exports2.isLikelyTestFile = exports2.sanitizeRepoRelPath = exports2.sha256Hex = exports2.hashFiles = exports2.enumerateTestFiles = exports2.buildNextManifest = exports2.detectRepoSwitch = exports2.classifyImportDiff = exports2.parseImportManifest = exports2.REPO_ANALYSIS_SCHEMA_VERSION = exports2.validateSourceEndpoints = exports2.validateAuthoredTest = exports2.validateRepoMapFile = exports2.validateRepoMemoryFile = exports2.buildTestingStandardsSection = exports2.buildRepoAuthorSystemRules = exports2.buildRepoAuthorKickoff = exports2.buildRepoAuthorContractMd = exports2.buildRepoMapSystemRules = exports2.buildRepoMapKickoff = exports2.buildRepoMapContractMd = exports2.buildRepoMemorySystemRules = exports2.buildRepoMemoryKickoff = exports2.buildRepoMemoryContractMd = exports2.runRepoAnalysisOnRunner = exports2.IMPORT_SCHEMA_VERSION = exports2.validateImportResultFile = exports2.validateImportedTest = exports2.buildImportSystemRules = exports2.buildImportKickoff = exports2.buildImportContractMd = exports2.MAX_ARCHIVE_BYTES = exports2.isUnsafeEntryName = void 0;
346791
- exports2.UNSUPPORTED_RENDERER_PREFIX = exports2.UnsupportedRendererError = exports2.buildCanvasModeAuthoringAddendum = exports2.buildUnsupportedRendererMessage = exports2.modelSupportsVision = exports2.decideCanvasStrategy = void 0;
347007
+ exports2.acquireRepoForImport = exports2.runFrameworkImportOnRunner = exports2.getAgenticEngine = exports2.resolveRefFromSnapshot = exports2.replayAuthRecipeIntoBrowser = exports2.runRegisterCaptureRun = exports2.runLoginCaptureRun = exports2.filterLoginCaptureCredentials = exports2.buildLoginCaptureCredentials = exports2.isReservedRunnerEnvVar = exports2.RESERVED_RUNNER_ENV_VARS = exports2.AUTH_MARKER_VERSION = exports2.buildHealAuthStamp = exports2.extractAuthRegion = exports2.upsertAuthBlock = exports2.buildAuthRecipe = exports2.commandToReplayStep = exports2.buildReplayPlan = exports2.absoluteRoute = exports2.renderReplaySettleArrow = exports2.renderReplayClickArrow = exports2.renderAuthFn = exports2.filterFeatureGroupsToPathScope = exports2.isAuthFlowRoute = exports2.deriveSurveyPathScope = exports2.canonicalizeRoute = exports2.executeGrokPerBatchHeal = exports2.executeGrokPerTestHeal = exports2.headerVal = exports2.classifyAuthType = exports2.isTextExcerptable = exports2.isStaticAssetByExt = exports2.isApiCall = exports2.buildApiCallSummary = exports2.computeMobileHealOutcome = exports2.detectMobileStepSecretLeak = exports2.redactMobileSecretsFromText = exports2.MOBILE_LIVE_HEAL_SYSTEM_PROMPT = exports2.MOBILE_SCRIPT_HEAL_SYSTEM_PROMPT = exports2.executeMobileLiveHeal = exports2.executeMobileTestWithHealing = exports2.executeMobileScriptDrivenHeal = exports2.prepareMobileScenarioAppState = exports2.runMobileGeneratePhase = exports2.runMobileAIPlanner = exports2.buildMobileSiteMap = exports2.formatMobileEvidenceForPlanner = exports2.MOBILE_SCENARIO_SYSTEM_PROMPT = exports2.MOBILE_SURVEY_SYSTEM_PROMPT = exports2.runMobileExplorePhase = void 0;
347008
+ exports2.isSafeCanonicalRedirect = exports2.resolveBaseUrlPreflight = exports2.checkBaseUrlRedirect = exports2.stripJsonFences = exports2.scrubCredentials = exports2.withAIRetry = exports2.snapshotMatchingChildren = exports2.findNewChromiumChildPid = exports2.killProcessTree = exports2.gracefulCloseWithTreeKill = exports2.liveBrowserRegistry = exports2.REPO_SWITCH_REMOVED_FRACTION = exports2.MAX_MANIFEST_FILES = exports2.toDiffSummary = exports2.isLikelyTestFile = exports2.sanitizeRepoRelPath = exports2.sha256Hex = exports2.hashFiles = exports2.enumerateTestFiles = exports2.buildNextManifest = exports2.detectRepoSwitch = exports2.classifyImportDiff = exports2.parseImportManifest = exports2.REPO_ANALYSIS_SCHEMA_VERSION = exports2.validateSourceEndpoints = exports2.validateAuthoredTest = exports2.validateRepoMapFile = exports2.validateRepoMemoryFile = exports2.buildTestingStandardsSection = exports2.buildRepoAuthorSystemRules = exports2.buildRepoAuthorKickoff = exports2.buildRepoAuthorContractMd = exports2.buildRepoMapSystemRules = exports2.buildRepoMapKickoff = exports2.buildRepoMapContractMd = exports2.buildRepoMemorySystemRules = exports2.buildRepoMemoryKickoff = exports2.buildRepoMemoryContractMd = exports2.runRepoAnalysisOnRunner = exports2.IMPORT_SCHEMA_VERSION = exports2.validateImportResultFile = exports2.validateImportedTest = exports2.buildImportSystemRules = exports2.buildImportKickoff = exports2.buildImportContractMd = exports2.MAX_ARCHIVE_BYTES = exports2.isUnsafeEntryName = exports2.extractZipSafely = exports2.downloadArchiveCapped = exports2.detectArchiveKind = void 0;
347009
+ exports2.UNSUPPORTED_RENDERER_PREFIX = exports2.UnsupportedRendererError = exports2.buildCanvasModeAuthoringAddendum = exports2.buildUnsupportedRendererMessage = exports2.modelSupportsVision = exports2.decideCanvasStrategy = exports2.decideAppRenderType = exports2.detectCanvasApp = exports2.detectRateLimitBlock = void 0;
346792
347010
  __exportStar(require_ai_provider(), exports2);
346793
347011
  __exportStar(require_mobile_types(), exports2);
346794
347012
  __exportStar(require_mobile_source_parser(), exports2);
@@ -346896,6 +347114,16 @@ var require_dist2 = __commonJS({
346896
347114
  Object.defineProperty(exports2, "canonicalizeRoute", { enumerable: true, get: function() {
346897
347115
  return exploration_parser_js_1.canonicalizeRoute;
346898
347116
  } });
347117
+ var survey_scope_js_1 = require_survey_scope();
347118
+ Object.defineProperty(exports2, "deriveSurveyPathScope", { enumerable: true, get: function() {
347119
+ return survey_scope_js_1.deriveSurveyPathScope;
347120
+ } });
347121
+ Object.defineProperty(exports2, "isAuthFlowRoute", { enumerable: true, get: function() {
347122
+ return survey_scope_js_1.isAuthFlowRoute;
347123
+ } });
347124
+ Object.defineProperty(exports2, "filterFeatureGroupsToPathScope", { enumerable: true, get: function() {
347125
+ return survey_scope_js_1.filterFeatureGroupsToPathScope;
347126
+ } });
346899
347127
  var auth_recipe_js_1 = require_auth_recipe();
346900
347128
  Object.defineProperty(exports2, "renderAuthFn", { enumerable: true, get: function() {
346901
347129
  return auth_recipe_js_1.renderAuthFn;
@@ -347169,7 +347397,7 @@ var require_package4 = __commonJS({
347169
347397
  "package.json"(exports2, module2) {
347170
347398
  module2.exports = {
347171
347399
  name: "@validate.qa/runner",
347172
- version: "1.0.48",
347400
+ version: "1.0.49",
347173
347401
  description: "validate local test runner - execute Playwright tests on your machine, connected to validate.qa cloud.",
347174
347402
  bin: {
347175
347403
  "validate-runner": "dist/cli.js"
package/dist/cli.mjs CHANGED
@@ -77293,7 +77293,7 @@ var require_discover_ai_planner = __commonJS({
77293
77293
  "../runner-core/dist/services/discover-ai-planner.js"(exports) {
77294
77294
  "use strict";
77295
77295
  Object.defineProperty(exports, "__esModule", { value: true });
77296
- exports.PLANNER_STRENGTHEN_GATE_PROMPT = exports.SCENARIO_SYSTEM_PROMPT = void 0;
77296
+ exports.PLANNER_STRENGTHEN_GATE_PROMPT = exports.SCENARIO_SYSTEM_PROMPT = exports.SURVEY_SYSTEM_PROMPT = void 0;
77297
77297
  exports.getAIPlannerMaxTokens = getAIPlannerMaxTokens;
77298
77298
  exports.getSnapshotConfidence = getSnapshotConfidence;
77299
77299
  exports.compressSiteMap = compressSiteMap;
@@ -77829,7 +77829,7 @@ Plan these LOGGED-OUT instead (keep their own establishment steps; do NOT assume
77829
77829
  }
77830
77830
  return sections.join("\n\n");
77831
77831
  }
77832
- var SURVEY_SYSTEM_PROMPT = `You are a feature analyst for a web application. You receive evidence from an AI explorer that mapped the entire site. Your job is to organize pages into meaningful feature groups.
77832
+ exports.SURVEY_SYSTEM_PROMPT = `You are a feature analyst for a web application. You receive evidence from an AI explorer that mapped the entire site. Your job is to organize pages into meaningful feature groups.
77833
77833
 
77834
77834
  ## Output Discipline
77835
77835
  Return ONLY one valid JSON object matching the schema below. Do NOT include <think> blocks, markdown fences, analysis, prose, or any text outside the JSON object. Put any brief explanation only in the JSON "reasoning" field.
@@ -77849,6 +77849,7 @@ Return ONLY one valid JSON object matching the schema below. Do NOT include <thi
77849
77849
  6. Do NOT merge pages into one feature just because they serve a similar broader goal. Treat areas as separate features when the evidence shows separate primary navigation destinations (e.g. separate sidebar links), separate route families below the app shell (e.g. /dashboard/documents/* vs /dashboard/reports/* are separate even though both are under /dashboard), or different CRUD entities/forms. Keep pages together when they share one navigational area and operate on the same entity (e.g. /applications, /applications/stats, /applications?filter= all belong together).
77850
77850
  7. Feature descriptions MUST reference actual element labels from the evidence
77851
77851
  8. Do NOT create standalone features for generic error pages (404, 500, access denied). If a business route is broken or crashes, keep it in its intended feature and note the failure in the description. Error pages do not need their own deep exploration run.
77852
+ 9. The Base URL's PATH is the survey's entry scope. When it is non-root (e.g. \`/shop/checkout\`), the operator is auditing that one flow in isolation: do NOT create featureGroups for parent paths, sibling paths, or guessed/nonexistent routes on the same host \u2014 even if the explorer's evidence wandered there. Auth pages the flow itself traverses (its login page, an \`/api/auth/*\` callback) belong INSIDE that flow's group, not in separate groups. When the Base URL path is \`/\`, group the whole app as usual.
77852
77853
 
77853
77854
  ## Output JSON Schema
77854
77855
  {
@@ -78601,7 +78602,7 @@ Keep the exact JSON schema { "featureGroups":[...], "scenarios":[...], "reasonin
78601
78602
  const agentModel = options.modelOverride ? (0, ai_provider_js_1.resolveModelForTask)(options.modelOverride, "agent") : (0, ai_provider_js_1.getModel)("agent");
78602
78603
  const plannerMaxTokens = getAIPlannerMaxTokens(agentModel);
78603
78604
  logs2.push(`AI planner [${context.mode}]: ${siteMap.pages.length} pages, ${evidence.recordedJourneys.length} journeys, budget ${context.testBudget}`);
78604
- const systemPrompt = options.systemPromptOverride ?? (context.mode === "survey" ? SURVEY_SYSTEM_PROMPT : exports.SCENARIO_SYSTEM_PROMPT);
78605
+ const systemPrompt = options.systemPromptOverride ?? (context.mode === "survey" ? exports.SURVEY_SYSTEM_PROMPT : exports.SCENARIO_SYSTEM_PROMPT);
78605
78606
  const userPrompt = options.userPromptOverride ?? formatEvidenceForPlanner(compressed, evidence, context);
78606
78607
  logs2.push(`AI planner: ~${Math.ceil(userPrompt.length / 3.5)} input tokens`);
78607
78608
  const aiClient = options.aiClient ?? (options.modelOverride ? (0, ai_provider_js_1.getRunnerClientForModel)(options.modelOverride) : (0, ai_provider_js_1.getRunnerAIClient)());
@@ -321572,6 +321573,168 @@ var require_evidence_harvester = __commonJS({
321572
321573
  }
321573
321574
  });
321574
321575
 
321576
+ // ../runner-core/dist/services/survey-scope.js
321577
+ var require_survey_scope = __commonJS({
321578
+ "../runner-core/dist/services/survey-scope.js"(exports) {
321579
+ "use strict";
321580
+ Object.defineProperty(exports, "__esModule", { value: true });
321581
+ exports.deriveSurveyPathScope = deriveSurveyPathScope;
321582
+ exports.isAuthFlowRoute = isAuthFlowRoute;
321583
+ exports.buildSurveyScopeBoundary = buildSurveyScopeBoundary;
321584
+ exports.filterFeatureGroupsToPathScope = filterFeatureGroupsToPathScope;
321585
+ var exploration_parser_js_1 = require_exploration_parser();
321586
+ function deriveSurveyPathScope(baseUrl) {
321587
+ const trimmed = (baseUrl ?? "").trim();
321588
+ if (!trimmed)
321589
+ return null;
321590
+ let parsed;
321591
+ try {
321592
+ parsed = new URL(trimmed);
321593
+ } catch {
321594
+ return null;
321595
+ }
321596
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:")
321597
+ return null;
321598
+ const hashRoute = parsed.hash.startsWith("#/") ? parsed.hash : "";
321599
+ const basePath = (0, exploration_parser_js_1.normalizeRouteForFocusScope)(`${parsed.pathname}${hashRoute}`);
321600
+ if (basePath === "/" || basePath === "/#")
321601
+ return null;
321602
+ return { origin: parsed.origin, basePath };
321603
+ }
321604
+ var AUTH_FLOW_SEGMENTS = /* @__PURE__ */ new Set([
321605
+ "auth",
321606
+ "oauth",
321607
+ "oauth2",
321608
+ "callback",
321609
+ "login",
321610
+ "log-in",
321611
+ "signin",
321612
+ "sign-in",
321613
+ "signup",
321614
+ "sign-up",
321615
+ "register",
321616
+ "logout",
321617
+ "log-out",
321618
+ "signout",
321619
+ "sign-out",
321620
+ "sso",
321621
+ "saml",
321622
+ "oidc"
321623
+ ]);
321624
+ function isAuthFlowRoute(route2) {
321625
+ const normalized = (0, exploration_parser_js_1.normalizeRouteForFocusScope)(route2).toLowerCase();
321626
+ return normalized.split(/[/#]/).some((segment) => AUTH_FLOW_SEGMENTS.has(segment));
321627
+ }
321628
+ function routePathForScope(route2, origin) {
321629
+ const trimmed = (route2 ?? "").trim();
321630
+ if (trimmed.startsWith("//")) {
321631
+ try {
321632
+ const parsed = new URL(`https:${trimmed}`);
321633
+ if (parsed.host !== new URL(origin).host)
321634
+ return null;
321635
+ return `${parsed.pathname}${parsed.search}${parsed.hash}`;
321636
+ } catch {
321637
+ return null;
321638
+ }
321639
+ }
321640
+ if (/^https?:\/\//i.test(trimmed)) {
321641
+ try {
321642
+ const parsed = new URL(trimmed);
321643
+ if (parsed.origin !== origin)
321644
+ return null;
321645
+ return `${parsed.pathname}${parsed.search}${parsed.hash}`;
321646
+ } catch {
321647
+ return trimmed;
321648
+ }
321649
+ }
321650
+ if (trimmed.startsWith("/") && trimmed.includes("..")) {
321651
+ try {
321652
+ const resolved = new URL(trimmed, "https://scope.invalid");
321653
+ return `${resolved.pathname}${resolved.search}${resolved.hash}`;
321654
+ } catch {
321655
+ return trimmed;
321656
+ }
321657
+ }
321658
+ return trimmed;
321659
+ }
321660
+ function buildSurveyScopeBoundary(baseUrl) {
321661
+ const scope = deriveSurveyPathScope(baseUrl);
321662
+ const lines = [
321663
+ '- **Never invent a URL.** Navigate only to pages the app presented (links/buttons you can click, per the rule above) or redirects it performs. Do not guess "common" routes and do not probe made-up routes to see what the error page does'
321664
+ ];
321665
+ if (scope) {
321666
+ lines.push(`- **The base URL PATH is the entry scope** (\`${scope.basePath}\`) \u2014 this survey audits that flow in ISOLATION. Do not truncate the path to visit a parent, and do not visit or catalog sibling paths on this host that in-scope pages never linked to. Pages the flow itself pushes you through (its login page, an auth/OAuth callback, a post-submit redirect) are in scope`);
321667
+ }
321668
+ return lines.join("\n");
321669
+ }
321670
+ function filterFeatureGroupsToPathScope(featureGroups, baseUrl) {
321671
+ const scope = deriveSurveyPathScope(baseUrl);
321672
+ if (!scope) {
321673
+ return { kept: featureGroups, droppedGroups: [], trimmedRoutes: [], trimmedPages: [], basePath: null };
321674
+ }
321675
+ const withinBase = (route2) => {
321676
+ const path = routePathForScope(route2, scope.origin);
321677
+ if (path === null)
321678
+ return false;
321679
+ if ((0, exploration_parser_js_1.routeIsWithinFocusAreas)(path, [scope.basePath]))
321680
+ return true;
321681
+ const norm = (0, exploration_parser_js_1.normalizeRouteForFocusScope)(path);
321682
+ return norm === `${scope.basePath}#` || norm.startsWith(`${scope.basePath}#/`);
321683
+ };
321684
+ const allowedAuthFlow = (route2) => {
321685
+ const path = routePathForScope(route2, scope.origin);
321686
+ return path !== null && isAuthFlowRoute(path);
321687
+ };
321688
+ const kept = [];
321689
+ const droppedGroups = [];
321690
+ const trimmedRoutes = [];
321691
+ const trimmedPages = [];
321692
+ for (const group of featureGroups) {
321693
+ const routes = Array.isArray(group.routes) ? group.routes : [];
321694
+ const keptRoutes = [];
321695
+ const removedRoutes = [];
321696
+ let anchored = false;
321697
+ for (const route2 of routes) {
321698
+ if (withinBase(route2)) {
321699
+ anchored = true;
321700
+ keptRoutes.push(route2);
321701
+ } else if (allowedAuthFlow(route2)) {
321702
+ keptRoutes.push(route2);
321703
+ } else {
321704
+ removedRoutes.push(route2);
321705
+ }
321706
+ }
321707
+ if (!anchored) {
321708
+ droppedGroups.push({ name: group.name, routes: [...routes] });
321709
+ continue;
321710
+ }
321711
+ let pages = group.pages;
321712
+ const removedPages = [];
321713
+ if (Array.isArray(group.pages)) {
321714
+ const keptPages = [];
321715
+ for (const page of group.pages) {
321716
+ if (withinBase(page.route) || allowedAuthFlow(page.route))
321717
+ keptPages.push(page);
321718
+ else
321719
+ removedPages.push(page.route);
321720
+ }
321721
+ pages = keptPages;
321722
+ }
321723
+ if (removedRoutes.length === 0 && removedPages.length === 0) {
321724
+ kept.push(group);
321725
+ } else {
321726
+ kept.push({ ...group, routes: keptRoutes, ...group.pages !== void 0 ? { pages } : {} });
321727
+ if (removedRoutes.length > 0)
321728
+ trimmedRoutes.push({ name: group.name, routes: removedRoutes });
321729
+ if (removedPages.length > 0)
321730
+ trimmedPages.push({ name: group.name, routes: removedPages });
321731
+ }
321732
+ }
321733
+ return { kept, droppedGroups, trimmedRoutes, trimmedPages, basePath: scope.basePath };
321734
+ }
321735
+ }
321736
+ });
321737
+
321575
321738
  // ../runner-core/dist/services/discover-explorer.js
321576
321739
  var require_discover_explorer = __commonJS({
321577
321740
  "../runner-core/dist/services/discover-explorer.js"(exports) {
@@ -321613,6 +321776,7 @@ var require_discover_explorer = __commonJS({
321613
321776
  var evidence_harvester_js_1 = require_evidence_harvester();
321614
321777
  var exploration_parser_js_1 = require_exploration_parser();
321615
321778
  var survey_link_frontier_js_1 = require_survey_link_frontier();
321779
+ var survey_scope_js_1 = require_survey_scope();
321616
321780
  var snapshot_context_compactor_js_2 = require_snapshot_context_compactor();
321617
321781
  Object.defineProperty(exports, "formatMCPToolResult", { enumerable: true, get: function() {
321618
321782
  return snapshot_context_compactor_js_2.formatMCPToolResult;
@@ -322040,6 +322204,7 @@ If a snapshot's readiness section exposes \`loading_indicators\`, \`skeleton_con
322040
322204
  - **Map, do not mutate.** You MAY click buttons that open modals, popups, or dropdowns for the purpose of CATALOGING their contents (capture_page). Do NOT fill forms, submit data, or click destructive buttons (save, delete, submit) - just catalog the elements and close the modal
322041
322205
  - **Modals/popups/banners**: If a cookie consent banner, onboarding tour, or overlay appears, dismiss it (click close/X/dismiss/accept) and continue. If it reappears on the next page, dismiss it again - some sites show consent banners on every navigation until accepted
322042
322206
  - **Stay on the same origin** as the base URL. Note external links in elements but do not follow them
322207
+ ${(0, survey_scope_js_1.buildSurveyScopeBoundary)(baseUrl)}
322043
322208
  - **OAuth/SSO**: If login redirects to an external provider (Google, GitHub), do not follow it. Note that the app uses OAuth and explore unauthenticated pages only
322044
322209
  - **Auth failure**: If login${isRegisterMode ? " or account registration" : ""} fails after 2 attempts, capture the auth page and explore unauthenticated pages. Note the failure in your \`finish_exploration\` summary
322045
322210
  - **Parameterized routes**: /users/1 and /users/2 are the same page template. Visit ONE instance, not all of them
@@ -325538,6 +325703,8 @@ var require_agentic_survey_prompts = __commonJS({
325538
325703
  exports.validateSurveyorOutput = validateSurveyorOutput;
325539
325704
  exports.buildSurveyAgentsMd = buildSurveyAgentsMd;
325540
325705
  exports.buildSurveyBriefMd = buildSurveyBriefMd;
325706
+ exports.buildClaudeSurveyKickoffPrompt = buildClaudeSurveyKickoffPrompt;
325707
+ var survey_scope_js_1 = require_survey_scope();
325541
325708
  var CRITICALITY_VALUES = ["critical", "high", "medium", "low"];
325542
325709
  function isString(v) {
325543
325710
  return typeof v === "string";
@@ -325717,9 +325884,40 @@ var require_agentic_survey_prompts = __commonJS({
325717
325884
  '- Click "delete" or destructive admin actions.',
325718
325885
  "- Leave the baseUrl domain except briefly for OAuth providers.",
325719
325886
  "",
325887
+ "## Navigation scope",
325888
+ "",
325889
+ "The Base URL in BRIEF.md is the ENTRY SCOPE \u2014 its full path, not just",
325890
+ "its domain. Open the exact Base URL first. From there, navigate ONLY to:",
325891
+ "",
325892
+ "- URLs the app actually presented to you: a link, button, or form",
325893
+ " visible in a snapshot of an in-scope page. (`browser_navigate` to a",
325894
+ " URL you saw as a link href is fine \u2014 composing one yourself is not.)",
325895
+ "- Redirects the app itself performs during those interactions,",
325896
+ " including login/OAuth/SSO callback routes needed to complete a flow.",
325897
+ "- The Base URL itself, to return to the start of the flow.",
325898
+ "",
325899
+ "NEVER invent a URL. Do NOT:",
325900
+ "- Parent-walk: truncating the Base URL path (base `/shop/checkout` \u2192",
325901
+ " visiting `/shop` or `/`) is out of scope even though it is same-origin.",
325902
+ "- Sibling-walk: do not visit or catalog neighbours of the Base URL path",
325903
+ " that in-scope pages never linked to.",
325904
+ '- Guess "common" routes (`/admin`, `/about`) or probe made-up routes',
325905
+ " (`/nonexistent`) to see what the error handling does. Do not create",
325906
+ " featureGroups for generic error/404 pages unless the app itself",
325907
+ " routes users there as part of a real flow.",
325908
+ "- Any other URL surgery: if the UI did not present it and the app did",
325909
+ " not redirect you to it, it does not exist for this survey.",
325910
+ "",
325911
+ "When the Base URL path is non-root (e.g. `/shop/checkout`, `/portal/billing`),",
325912
+ "the operator is auditing THAT flow in isolation: every featureGroup must",
325913
+ "describe that flow. Auth pages the flow pushes you through belong inside",
325914
+ "that flow's featureGroup \u2014 do not mint separate featureGroups for them.",
325915
+ "When the Base URL is the origin root, survey the whole app \u2014 the",
325916
+ "no-guessing rule still applies: reach pages through the UI.",
325917
+ "",
325720
325918
  "## Exploration philosophy",
325721
325919
  "",
325722
- "- Breadth before depth \u2014 map the whole surface before drilling in.",
325920
+ "- Breadth before depth \u2014 map the whole in-scope surface before drilling in.",
325723
325921
  "- Authenticated and unauthenticated surfaces are different products;",
325724
325922
  " explore both when credentials are available.",
325725
325923
  "- Prefer observing over interacting: open a page, snapshot, note the",
@@ -325814,6 +326012,17 @@ var require_agentic_survey_prompts = __commonJS({
325814
326012
  lines.push("");
325815
326013
  lines.push(`**Base URL:** ${input.baseUrl}`);
325816
326014
  lines.push("");
326015
+ const pathScope = (0, survey_scope_js_1.deriveSurveyPathScope)(input.baseUrl);
326016
+ if (pathScope) {
326017
+ lines.push(`**Entry scope:** \`${pathScope.basePath}\` \u2014 this is a SCOPED survey of one flow.`);
326018
+ lines.push("");
326019
+ lines.push(`Everything you catalog must live under \`${pathScope.basePath}\`, or be a page`);
326020
+ lines.push("this flow itself pushes you through (its own login page, an auth/OAuth");
326021
+ lines.push("callback, a post-submit redirect). Parent and sibling paths on this host");
326022
+ lines.push("are OTHER fixtures/apps and are out of scope \u2014 do not visit them, do not");
326023
+ lines.push("catalog them, do not mint featureGroups for them.");
326024
+ lines.push("");
326025
+ }
325817
326026
  if (input.appBrief && input.appBrief.trim().length > 0) {
325818
326027
  lines.push("## App brief from the operator");
325819
326028
  lines.push("");
@@ -325883,6 +326092,37 @@ var require_agentic_survey_prompts = __commonJS({
325883
326092
  lines.push("");
325884
326093
  return lines.join("\n");
325885
326094
  }
326095
+ function buildClaudeSurveyKickoffPrompt(resultFilePath) {
326096
+ return [
326097
+ "Read AGENTS.md, then read BRIEF.md.",
326098
+ "",
326099
+ `Your deliverable is a SurveyorOutput JSON file at ${resultFilePath}.`,
326100
+ "Follow this procedure exactly:",
326101
+ "",
326102
+ "STEP 1 \u2014 Orient: navigate to the EXACT Base URL from BRIEF.md and",
326103
+ " snapshot it, then open 2-3 pages its UI visibly links to (e.g. a login",
326104
+ " link it presents). Navigate only to URLs the page actually presented \u2014",
326105
+ ' never guessed parent, sibling, or "common" routes (see "Navigation',
326106
+ ' scope" in AGENTS.md).',
326107
+ `STEP 2 \u2014 IMMEDIATELY use the Write tool to save a first COMPLETE draft of`,
326108
+ ` the SurveyorOutput JSON to ${resultFilePath}, based on what you have seen`,
326109
+ " so far. featureGroups must be a non-empty array. Do NOT skip this step",
326110
+ " and do NOT explore further until this file exists.",
326111
+ "STEP 3 \u2014 Continue exploring breadth-first. Visit each remaining page AT",
326112
+ " MOST ONCE \u2014 never re-navigate to or re-snapshot a page already seen.",
326113
+ ` After every 3-4 new pages, OVERWRITE ${resultFilePath} with an updated,`,
326114
+ " complete JSON object.",
326115
+ "STEP 4 \u2014 Stop once you have covered the main in-scope pages (around 20",
326116
+ ` page visits is plenty) or stop finding new features. Do a final OVERWRITE`,
326117
+ ` of ${resultFilePath} with the complete SurveyorOutput, then exit.`,
326118
+ "",
326119
+ "Budget: about 40 tool calls total. If you ever sense you are running low,",
326120
+ `immediately overwrite ${resultFilePath} with your best current JSON and`,
326121
+ "exit \u2014 a complete-but-shallow file always beats no file. Never write an",
326122
+ "empty featureGroups array and never omit the key. No markdown fences, no",
326123
+ "prose in the file \u2014 a single JSON object only."
326124
+ ].join("\n");
326125
+ }
325886
326126
  }
325887
326127
  });
325888
326128
 
@@ -326433,6 +326673,9 @@ ${(0, inbox_tools_js_1.buildInboxAgentsGuidance)(inboxReg.email, inboxReg.passwo
326433
326673
  const kickoffPrompt = [
326434
326674
  "Read AGENTS.md, then read BRIEF.md.",
326435
326675
  "Use the playwright MCP tools to explore the site.",
326676
+ 'Follow the "Navigation scope" rules in AGENTS.md: start at the exact Base',
326677
+ 'URL and never navigate to guessed parent, sibling, or "common" routes \u2014',
326678
+ "only URLs the pages actually present (or redirects the app performs).",
326436
326679
  `When done, write your JSON output to ${resultFilePath} as a single JSON object matching the SurveyorOutput schema in AGENTS.md.`,
326437
326680
  "Then exit."
326438
326681
  ].join("\n");
@@ -330149,32 +330392,7 @@ ${(0, inbox_tools_js_1.buildInboxAgentsGuidance)(inboxReg.email, inboxReg.passwo
330149
330392
  await (0, promises_1.writeFile)((0, node_path_1.join)(runDir, "AGENTS.md"), surveyAgentsMd, "utf8");
330150
330393
  await (0, promises_1.writeFile)((0, node_path_1.join)(runDir, "BRIEF.md"), (0, agentic_survey_prompts_js_1.buildSurveyBriefMd)(briefInput), "utf8");
330151
330394
  log2("Wrote AGENTS.md + BRIEF.md");
330152
- const kickoffPrompt = [
330153
- "Read AGENTS.md, then read BRIEF.md.",
330154
- "",
330155
- `Your deliverable is a SurveyorOutput JSON file at ${resultFilePath}.`,
330156
- "Follow this procedure exactly:",
330157
- "",
330158
- "STEP 1 \u2014 Orient: navigate to the base URL and 2-3 obvious pages (e.g. the",
330159
- " login page and the main dashboard).",
330160
- `STEP 2 \u2014 IMMEDIATELY use the Write tool to save a first COMPLETE draft of`,
330161
- ` the SurveyorOutput JSON to ${resultFilePath}, based on what you have seen`,
330162
- " so far. featureGroups must be a non-empty array. Do NOT skip this step",
330163
- " and do NOT explore further until this file exists.",
330164
- "STEP 3 \u2014 Continue exploring breadth-first. Visit each remaining page AT",
330165
- " MOST ONCE \u2014 never re-navigate to or re-snapshot a page already seen.",
330166
- ` After every 3-4 new pages, OVERWRITE ${resultFilePath} with an updated,`,
330167
- " complete JSON object.",
330168
- "STEP 4 \u2014 Stop once you have covered the main pages (around 20 page visits",
330169
- ` is plenty) or stop finding new features. Do a final OVERWRITE of`,
330170
- ` ${resultFilePath} with the complete SurveyorOutput, then exit.`,
330171
- "",
330172
- "Budget: about 40 tool calls total. If you ever sense you are running low,",
330173
- `immediately overwrite ${resultFilePath} with your best current JSON and`,
330174
- "exit \u2014 a complete-but-shallow file always beats no file. Never write an",
330175
- "empty featureGroups array and never omit the key. No markdown fences, no",
330176
- "prose in the file \u2014 a single JSON object only."
330177
- ].join("\n");
330395
+ const kickoffPrompt = (0, agentic_survey_prompts_js_1.buildClaudeSurveyKickoffPrompt)(resultFilePath);
330178
330396
  const credentialScrubMap = {};
330179
330397
  for (const c of credentials) {
330180
330398
  credentialScrubMap[c.email] = "<email>";
@@ -346791,9 +347009,9 @@ var require_dist2 = __commonJS({
346791
347009
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports2, p)) __createBinding(exports2, m, p);
346792
347010
  };
346793
347011
  Object.defineProperty(exports, "__esModule", { value: true });
346794
- exports.extractZipSafely = exports.downloadArchiveCapped = exports.detectArchiveKind = exports.acquireRepoForImport = exports.runFrameworkImportOnRunner = exports.getAgenticEngine = exports.resolveRefFromSnapshot = exports.replayAuthRecipeIntoBrowser = exports.runRegisterCaptureRun = exports.runLoginCaptureRun = exports.filterLoginCaptureCredentials = exports.buildLoginCaptureCredentials = exports.isReservedRunnerEnvVar = exports.RESERVED_RUNNER_ENV_VARS = exports.AUTH_MARKER_VERSION = exports.buildHealAuthStamp = exports.extractAuthRegion = exports.upsertAuthBlock = exports.buildAuthRecipe = exports.commandToReplayStep = exports.buildReplayPlan = exports.absoluteRoute = exports.renderReplaySettleArrow = exports.renderReplayClickArrow = exports.renderAuthFn = exports.canonicalizeRoute = exports.executeGrokPerBatchHeal = exports.executeGrokPerTestHeal = exports.headerVal = exports.classifyAuthType = exports.isTextExcerptable = exports.isStaticAssetByExt = exports.isApiCall = exports.buildApiCallSummary = exports.computeMobileHealOutcome = exports.detectMobileStepSecretLeak = exports.redactMobileSecretsFromText = exports.MOBILE_LIVE_HEAL_SYSTEM_PROMPT = exports.MOBILE_SCRIPT_HEAL_SYSTEM_PROMPT = exports.executeMobileLiveHeal = exports.executeMobileTestWithHealing = exports.executeMobileScriptDrivenHeal = exports.prepareMobileScenarioAppState = exports.runMobileGeneratePhase = exports.runMobileAIPlanner = exports.buildMobileSiteMap = exports.formatMobileEvidenceForPlanner = exports.MOBILE_SCENARIO_SYSTEM_PROMPT = exports.MOBILE_SURVEY_SYSTEM_PROMPT = exports.runMobileExplorePhase = void 0;
346795
- exports.decideAppRenderType = exports.detectCanvasApp = exports.detectRateLimitBlock = exports.isSafeCanonicalRedirect = exports.resolveBaseUrlPreflight = exports.checkBaseUrlRedirect = exports.stripJsonFences = exports.scrubCredentials = exports.withAIRetry = exports.snapshotMatchingChildren = exports.findNewChromiumChildPid = exports.killProcessTree = exports.gracefulCloseWithTreeKill = exports.liveBrowserRegistry = exports.REPO_SWITCH_REMOVED_FRACTION = exports.MAX_MANIFEST_FILES = exports.toDiffSummary = exports.isLikelyTestFile = exports.sanitizeRepoRelPath = exports.sha256Hex = exports.hashFiles = exports.enumerateTestFiles = exports.buildNextManifest = exports.detectRepoSwitch = exports.classifyImportDiff = exports.parseImportManifest = exports.REPO_ANALYSIS_SCHEMA_VERSION = exports.validateSourceEndpoints = exports.validateAuthoredTest = exports.validateRepoMapFile = exports.validateRepoMemoryFile = exports.buildTestingStandardsSection = exports.buildRepoAuthorSystemRules = exports.buildRepoAuthorKickoff = exports.buildRepoAuthorContractMd = exports.buildRepoMapSystemRules = exports.buildRepoMapKickoff = exports.buildRepoMapContractMd = exports.buildRepoMemorySystemRules = exports.buildRepoMemoryKickoff = exports.buildRepoMemoryContractMd = exports.runRepoAnalysisOnRunner = exports.IMPORT_SCHEMA_VERSION = exports.validateImportResultFile = exports.validateImportedTest = exports.buildImportSystemRules = exports.buildImportKickoff = exports.buildImportContractMd = exports.MAX_ARCHIVE_BYTES = exports.isUnsafeEntryName = void 0;
346796
- exports.UNSUPPORTED_RENDERER_PREFIX = exports.UnsupportedRendererError = exports.buildCanvasModeAuthoringAddendum = exports.buildUnsupportedRendererMessage = exports.modelSupportsVision = exports.decideCanvasStrategy = void 0;
347012
+ exports.acquireRepoForImport = exports.runFrameworkImportOnRunner = exports.getAgenticEngine = exports.resolveRefFromSnapshot = exports.replayAuthRecipeIntoBrowser = exports.runRegisterCaptureRun = exports.runLoginCaptureRun = exports.filterLoginCaptureCredentials = exports.buildLoginCaptureCredentials = exports.isReservedRunnerEnvVar = exports.RESERVED_RUNNER_ENV_VARS = exports.AUTH_MARKER_VERSION = exports.buildHealAuthStamp = exports.extractAuthRegion = exports.upsertAuthBlock = exports.buildAuthRecipe = exports.commandToReplayStep = exports.buildReplayPlan = exports.absoluteRoute = exports.renderReplaySettleArrow = exports.renderReplayClickArrow = exports.renderAuthFn = exports.filterFeatureGroupsToPathScope = exports.isAuthFlowRoute = exports.deriveSurveyPathScope = exports.canonicalizeRoute = exports.executeGrokPerBatchHeal = exports.executeGrokPerTestHeal = exports.headerVal = exports.classifyAuthType = exports.isTextExcerptable = exports.isStaticAssetByExt = exports.isApiCall = exports.buildApiCallSummary = exports.computeMobileHealOutcome = exports.detectMobileStepSecretLeak = exports.redactMobileSecretsFromText = exports.MOBILE_LIVE_HEAL_SYSTEM_PROMPT = exports.MOBILE_SCRIPT_HEAL_SYSTEM_PROMPT = exports.executeMobileLiveHeal = exports.executeMobileTestWithHealing = exports.executeMobileScriptDrivenHeal = exports.prepareMobileScenarioAppState = exports.runMobileGeneratePhase = exports.runMobileAIPlanner = exports.buildMobileSiteMap = exports.formatMobileEvidenceForPlanner = exports.MOBILE_SCENARIO_SYSTEM_PROMPT = exports.MOBILE_SURVEY_SYSTEM_PROMPT = exports.runMobileExplorePhase = void 0;
347013
+ exports.isSafeCanonicalRedirect = exports.resolveBaseUrlPreflight = exports.checkBaseUrlRedirect = exports.stripJsonFences = exports.scrubCredentials = exports.withAIRetry = exports.snapshotMatchingChildren = exports.findNewChromiumChildPid = exports.killProcessTree = exports.gracefulCloseWithTreeKill = exports.liveBrowserRegistry = exports.REPO_SWITCH_REMOVED_FRACTION = exports.MAX_MANIFEST_FILES = exports.toDiffSummary = exports.isLikelyTestFile = exports.sanitizeRepoRelPath = exports.sha256Hex = exports.hashFiles = exports.enumerateTestFiles = exports.buildNextManifest = exports.detectRepoSwitch = exports.classifyImportDiff = exports.parseImportManifest = exports.REPO_ANALYSIS_SCHEMA_VERSION = exports.validateSourceEndpoints = exports.validateAuthoredTest = exports.validateRepoMapFile = exports.validateRepoMemoryFile = exports.buildTestingStandardsSection = exports.buildRepoAuthorSystemRules = exports.buildRepoAuthorKickoff = exports.buildRepoAuthorContractMd = exports.buildRepoMapSystemRules = exports.buildRepoMapKickoff = exports.buildRepoMapContractMd = exports.buildRepoMemorySystemRules = exports.buildRepoMemoryKickoff = exports.buildRepoMemoryContractMd = exports.runRepoAnalysisOnRunner = exports.IMPORT_SCHEMA_VERSION = exports.validateImportResultFile = exports.validateImportedTest = exports.buildImportSystemRules = exports.buildImportKickoff = exports.buildImportContractMd = exports.MAX_ARCHIVE_BYTES = exports.isUnsafeEntryName = exports.extractZipSafely = exports.downloadArchiveCapped = exports.detectArchiveKind = void 0;
347014
+ exports.UNSUPPORTED_RENDERER_PREFIX = exports.UnsupportedRendererError = exports.buildCanvasModeAuthoringAddendum = exports.buildUnsupportedRendererMessage = exports.modelSupportsVision = exports.decideCanvasStrategy = exports.decideAppRenderType = exports.detectCanvasApp = exports.detectRateLimitBlock = void 0;
346797
347015
  __exportStar(require_ai_provider(), exports);
346798
347016
  __exportStar(require_mobile_types(), exports);
346799
347017
  __exportStar(require_mobile_source_parser(), exports);
@@ -346901,6 +347119,16 @@ var require_dist2 = __commonJS({
346901
347119
  Object.defineProperty(exports, "canonicalizeRoute", { enumerable: true, get: function() {
346902
347120
  return exploration_parser_js_1.canonicalizeRoute;
346903
347121
  } });
347122
+ var survey_scope_js_1 = require_survey_scope();
347123
+ Object.defineProperty(exports, "deriveSurveyPathScope", { enumerable: true, get: function() {
347124
+ return survey_scope_js_1.deriveSurveyPathScope;
347125
+ } });
347126
+ Object.defineProperty(exports, "isAuthFlowRoute", { enumerable: true, get: function() {
347127
+ return survey_scope_js_1.isAuthFlowRoute;
347128
+ } });
347129
+ Object.defineProperty(exports, "filterFeatureGroupsToPathScope", { enumerable: true, get: function() {
347130
+ return survey_scope_js_1.filterFeatureGroupsToPathScope;
347131
+ } });
346904
347132
  var auth_recipe_js_1 = require_auth_recipe();
346905
347133
  Object.defineProperty(exports, "renderAuthFn", { enumerable: true, get: function() {
346906
347134
  return auth_recipe_js_1.renderAuthFn;
@@ -347174,7 +347402,7 @@ var require_package4 = __commonJS({
347174
347402
  "package.json"(exports, module) {
347175
347403
  module.exports = {
347176
347404
  name: "@validate.qa/runner",
347177
- version: "1.0.48",
347405
+ version: "1.0.49",
347178
347406
  description: "validate local test runner - execute Playwright tests on your machine, connected to validate.qa cloud.",
347179
347407
  bin: {
347180
347408
  "validate-runner": "dist/cli.js"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@validate.qa/runner",
3
- "version": "1.0.48",
3
+ "version": "1.0.49",
4
4
  "description": "validate local test runner - execute Playwright tests on your machine, connected to validate.qa cloud.",
5
5
  "bin": {
6
6
  "validate-runner": "dist/cli.js"