@validate.qa/runner 1.0.74 → 1.0.76

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 +48 -28
  2. package/dist/cli.mjs +48 -28
  3. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -320970,6 +320970,7 @@ var require_primary_action_ledger = __commonJS({
320970
320970
  exports2.getBlockingActionDebt = getBlockingActionDebt;
320971
320971
  exports2.identityLabelForKey = identityLabelForKey;
320972
320972
  exports2.entryMatchesClick = entryMatchesClick;
320973
+ exports2.computeNewlyClicked = computeNewlyClicked;
320973
320974
  exports2.reconcileCapture = reconcileCapture;
320974
320975
  exports2.classifyActionOutcome = classifyActionOutcome;
320975
320976
  exports2.detectPrimaryActions = detectPrimaryActions;
@@ -320997,7 +320998,7 @@ var require_primary_action_ledger = __commonJS({
320997
320998
  exports2.AMBIGUOUS_ACTION_VERB_RE = /^(continue|next|apply|go|proceed|confirm|update|add|edit|ok|done|finish|complete|approve|enable|activate)\b/i;
320998
320999
  exports2.DESTRUCTIVE_ACTION_VERB_RE = /^(delete|remove|archive|reset|disconnect|deactivate|destroy|purge|revoke|discard|drop|wipe|erase|unsubscribe|cancel|clear|trash)\b/i;
320999
321000
  exports2.PAID_ACTION_RE = /\b(pay|payment|checkout|check\s*out|subscribe|subscription|purchase|buy|upgrade|billing|(?:submit|place|confirm|complete|review)\s+(?:order|purchase|payment|booking)|(?:start|activate|begin|choose|select|try)\s+(?:a\s+|your\s+|the\s+)?(?:free\s+)?(?:subscription|trial|plan|membership|billing)|free\s+trial|send\s+(?:money|funds|cash|a\s+payment|payment|a\s+gift|gift)|order\s*now|donate|tip|renew|add\s*funds|add\s+to\s+cart|withdraw|deposit|transfer\s+(?:funds|money))\b/i;
321000
- exports2.PROD_SIDE_EFFECT_RE = /\b(deploy|publish|push|ship|release|promote|roll\s*back|rollback)\b[^.]*\bto\b[^.]*\b(prod|production|live|mainnet)\b/i;
321001
+ exports2.PROD_SIDE_EFFECT_RE = /\b(deploy|publish|push|ship|release|promote|roll\s*back|rollback)\b[^.\n;:,—–]{0,40}\bto\b[^.\n;:,—–]{0,40}\b(prod|production|live|mainnet)\b/i;
321001
321002
  exports2.MESSAGE_PEOPLE_RE = /\b(invite|(?:send|resend)\s+(?:a\s+|an\s+|the\s+|new\s+|your\s+)?(?:invitation|invite|email|message|sms|text|reminder|notification|note|campaign|newsletter|broadcast|blast|bulk\s+email)|(?:send|email|message|text|notify)\b[^.]*\bto\s+(?:all|everyone|subscribers|the\s+list|your\s+list|contacts|customers|the\s+team)\b|share\s*via|notify\s*(?:team|user|member|people|everyone)|(?:email|message|text)\s+(?:the|a|all|your|team|user|customer|member))\b/i;
321002
321003
  function leadingVerb(label) {
321003
321004
  if (!label)
@@ -321103,10 +321104,13 @@ var require_primary_action_ledger = __commonJS({
321103
321104
  function normLabelKey(label) {
321104
321105
  return label.trim().toLowerCase().replace(/\s+/g, " ");
321105
321106
  }
321107
+ function stableLabelStem(label) {
321108
+ return normLabelKey(label).replace(/\s*\(\d{1,3}\)\s*$/, "").replace(/(?:\s*\.\.\.|…)\s*$/, "").replace(/\s+/g, " ").trim();
321109
+ }
321106
321110
  function identityLabelForKey(containerScope, label) {
321107
321111
  if (containerScope && GRID_SCOPE_RE.test(containerScope))
321108
321112
  return "";
321109
- return normLabelKey(label);
321113
+ return stableLabelStem(label);
321110
321114
  }
321111
321115
  function entryMatchesClick(entry, clickedLabels) {
321112
321116
  if (clickedLabels.has(entry.label))
@@ -321118,6 +321122,13 @@ var require_primary_action_ledger = __commonJS({
321118
321122
  }
321119
321123
  return false;
321120
321124
  }
321125
+ function computeNewlyClicked(prevCounts, curCounts) {
321126
+ const out = /* @__PURE__ */ new Set();
321127
+ for (const [label, n] of curCounts)
321128
+ if (n > (prevCounts.get(label) ?? 0))
321129
+ out.add(label);
321130
+ return out;
321131
+ }
321121
321132
  function reconcileCapture(input) {
321122
321133
  const nCur = normalizeActionRoute(input.route);
321123
321134
  const nPrev = input.previousRoute ? normalizeActionRoute(input.previousRoute) : null;
@@ -321133,36 +321144,38 @@ var require_primary_action_ledger = __commonJS({
321133
321144
  }
321134
321145
  }
321135
321146
  if (input.prevSignatures) {
321136
- const activeThisInterval = [];
321147
+ const matching = [];
321137
321148
  for (const e of input.ledger.values()) {
321138
321149
  if (e.kind !== "primary" || e.isExempt)
321139
321150
  continue;
321140
- if (e.status === "OUTCOME_CAPTURED" || e.status === "BLOCKED")
321141
- continue;
321142
321151
  if (normalizeActionRoute(e.route) !== nCur)
321143
321152
  continue;
321144
321153
  if (entryMatchesClick(e, input.newlyClickedThisRoute))
321145
- activeThisInterval.push(e.id);
321154
+ matching.push(e);
321146
321155
  }
321147
- if (activeThisInterval.length === 1) {
321156
+ const active = matching.filter((e) => e.status !== "OUTCOME_CAPTURED" && e.status !== "BLOCKED");
321157
+ if (matching.length === 1 && active.length === 1) {
321148
321158
  const revealed = (0, reveal_diff_js_1.computeRevealedControls)(input.prevSignatures, input.currentSignatures);
321149
321159
  if (revealed.size > 0)
321150
- outcomes.push({ id: activeThisInterval[0], signal: `revealed:${revealed.size}` });
321160
+ outcomes.push({ id: active[0].id, signal: `revealed:${revealed.size}` });
321151
321161
  }
321152
321162
  }
321153
321163
  if (nPrev && nPrev !== nCur && input.transitionTriggerLabels.size > 0) {
321164
+ const matching = [];
321154
321165
  for (const e of input.ledger.values()) {
321155
321166
  if (e.kind !== "primary" || e.isExempt)
321156
321167
  continue;
321157
- if (e.status === "OUTCOME_CAPTURED" || e.status === "BLOCKED")
321158
- continue;
321159
321168
  if (normalizeActionRoute(e.route) !== nPrev)
321160
321169
  continue;
321161
- if (entryMatchesClick(e, input.transitionTriggerLabels)) {
321162
- if (e.status === "DETECTED")
321163
- attempts.push(e.id);
321164
- outcomes.push({ id: e.id, signal: "route-change" });
321165
- }
321170
+ if (entryMatchesClick(e, input.transitionTriggerLabels))
321171
+ matching.push(e);
321172
+ }
321173
+ const active = matching.filter((e) => e.status !== "OUTCOME_CAPTURED" && e.status !== "BLOCKED");
321174
+ if (matching.length === 1 && active.length === 1) {
321175
+ const e = active[0];
321176
+ if (e.status === "DETECTED")
321177
+ attempts.push(e.id);
321178
+ outcomes.push({ id: e.id, signal: "route-change" });
321166
321179
  }
321167
321180
  }
321168
321181
  return { attempts, outcomes };
@@ -321281,6 +321294,7 @@ var require_exploration_state = __commonJS({
321281
321294
  testedRoutes: /* @__PURE__ */ new Set(),
321282
321295
  routeState: /* @__PURE__ */ new Map(),
321283
321296
  actionLedger: /* @__PURE__ */ new Map(),
321297
+ actionLedgerDropped: 0,
321284
321298
  frontier: /* @__PURE__ */ new Map(),
321285
321299
  recordedJourneys: [],
321286
321300
  suggestCommandCursors: /* @__PURE__ */ new Map(),
@@ -321321,6 +321335,7 @@ var require_exploration_state = __commonJS({
321321
321335
  recentProgressSignals: [],
321322
321336
  lastMeaningfulAction: null,
321323
321337
  interactedElements: /* @__PURE__ */ new Set(),
321338
+ interactionCounts: /* @__PURE__ */ new Map(),
321324
321339
  discoveredSubNav: [],
321325
321340
  exploredSubNav: /* @__PURE__ */ new Set(),
321326
321341
  failedActions: [],
@@ -321425,6 +321440,7 @@ var require_exploration_state = __commonJS({
321425
321440
  if (nameMatch) {
321426
321441
  const label = nameMatch[1];
321427
321442
  rs.interactedElements.add(label);
321443
+ rs.interactionCounts.set(label, (rs.interactionCounts.get(label) ?? 0) + 1);
321428
321444
  if (rs.discoveredSubNav.some((s) => s.label === label)) {
321429
321445
  rs.exploredSubNav.add(label);
321430
321446
  }
@@ -321467,8 +321483,10 @@ var require_exploration_state = __commonJS({
321467
321483
  const key = (0, primary_action_ledger_js_1.primaryActionKey)(event.route, c.verb, c.role, a.containerScope, c.kind, c.exemptReason, a.identityLabel);
321468
321484
  const existing = state2.actionLedger.get(key);
321469
321485
  if (!existing) {
321470
- if (state2.actionLedger.size >= LEDGER_CAP)
321486
+ if (state2.actionLedger.size >= LEDGER_CAP) {
321487
+ state2.actionLedgerDropped++;
321471
321488
  continue;
321489
+ }
321472
321490
  const entry = (0, primary_action_ledger_js_1.makeLedgerEntry)(key, c, event.route, a.containerScope, state2.iteration);
321473
321491
  entry.collapsed = Math.max(1, a.count);
321474
321492
  state2.actionLedger.set(key, entry);
@@ -322724,7 +322742,8 @@ var require_exploration_prompt_builder = __commonJS({
322724
322742
  const out = [
322725
322743
  "",
322726
322744
  "### Primary Actions Not Yet Exercised",
322727
- "These are the feature's core actions. Exercise each SAFE one: click it, wait for it to finish, then capture_page the result (a new panel, row, status, or route change). Do NOT click anything that would spend money, message real people, deploy to production, or permanently delete data \u2014 note it as blocked and move on. Don't finish until each safe action has produced an observed outcome:"
322745
+ "These are the feature's core actions \u2014 what it EXISTS to do. You are on a PROVISIONED TEST ACCOUNT, so exercising them is the point of this exploration. For each: click it, wait for it to finish (an async job can take 10-60s \u2014 keep waiting and capturing, do not give up early), then capture_page the result (a new panel, row, score, status, or route change).",
322746
+ `Spending in-app COMPUTE credits is expected and budgeted \u2014 running an Analyze / Generate / Run / Process ONCE to observe its output is exactly what you should do; an "N of M used" usage or AI-credit meter is NOT a reason to skip. These were auto-screened, but YOU are the final check: if you can see a specific listed action actually charges REAL money at a checkout (Buy / Upgrade / Subscribe), makes an irreversible or destructive change (Delete / Wipe / Deploy to production), or sends a message to real people (Send email / Invite), SKIP just that one \u2014 note it as blocked with the reason and move on. Don't finish until every other action below has produced an observed outcome:`
322728
322747
  ];
322729
322748
  for (const e of debt.slice(0, 8)) {
322730
322749
  const note = e.status === "ATTEMPTED" ? " \u2014 clicked but no result captured yet" : "";
@@ -326547,8 +326566,9 @@ ${inbox_tools_js_1.EMAIL_VERIFICATION_UNAVAILABLE_OVERRIDE_BLOCK}` : ""}`;
326547
326566
  const primaryActionLedgerActive = (0, primary_action_ledger_js_1.isLedgerActive)(primaryActionMode) && options?.mode === "deep";
326548
326567
  const primaryActionEnforcing = primaryActionLedgerActive && (0, primary_action_ledger_js_1.isGateEnforcing)(primaryActionMode);
326549
326568
  const prevRouteSignatures = /* @__PURE__ */ new Map();
326550
- const prevInteractedByRoute = /* @__PURE__ */ new Map();
326569
+ const prevInteractionCountsByRoute = /* @__PURE__ */ new Map();
326551
326570
  let previousCaptureRoute = null;
326571
+ let previousCaptureTransitionCount = 0;
326552
326572
  const linkScope = (0, survey_link_frontier_js_1.deriveLinkScope)(baseUrl);
326553
326573
  const surveyNavScope = options?.mode === "survey" ? (0, survey_scope_js_1.deriveSurveyPathScope)(baseUrl) : null;
326554
326574
  let finishRedirects = 0;
@@ -327519,16 +327539,15 @@ ${currentSummary}`;
327519
327539
  }
327520
327540
  const currentSigs = (0, reveal_diff_js_1.buildSnapshotSignatures)(capElements);
327521
327541
  const cumClicked = state2.routeState.get(capRoute)?.interactedElements ?? /* @__PURE__ */ new Set();
327522
- const prevClicked = prevInteractedByRoute.get(capRoute) ?? /* @__PURE__ */ new Set();
327523
- const newlyClicked = /* @__PURE__ */ new Set();
327524
- for (const l of cumClicked)
327525
- if (!prevClicked.has(l))
327526
- newlyClicked.add(l);
327542
+ const curCounts = state2.routeState.get(capRoute)?.interactionCounts ?? /* @__PURE__ */ new Map();
327543
+ const prevCounts = prevInteractionCountsByRoute.get(capRoute) ?? /* @__PURE__ */ new Map();
327544
+ const newlyClicked = (0, primary_action_ledger_js_1.computeNewlyClicked)(prevCounts, curCounts);
327527
327545
  const transitionTriggerLabels = /* @__PURE__ */ new Set();
327528
327546
  if (previousCaptureRoute && (0, exploration_parser_js_1.normalizeRouteForFocusScope)(previousCaptureRoute) !== (0, exploration_parser_js_1.normalizeRouteForFocusScope)(capRoute)) {
327529
327547
  const nPrevCap = (0, exploration_parser_js_1.normalizeRouteForFocusScope)(previousCaptureRoute);
327530
327548
  const nCap = (0, exploration_parser_js_1.normalizeRouteForFocusScope)(capRoute);
327531
- for (const t of state2.collectedTransitions) {
327549
+ for (let ti = previousCaptureTransitionCount; ti < state2.collectedTransitions.length; ti++) {
327550
+ const t = state2.collectedTransitions[ti];
327532
327551
  if (!t.triggerLabel)
327533
327552
  continue;
327534
327553
  if ((0, exploration_parser_js_1.normalizeRouteForFocusScope)(t.fromRoute) === nPrevCap && (0, exploration_parser_js_1.normalizeRouteForFocusScope)(t.toRoute) === nCap) {
@@ -327551,8 +327570,9 @@ ${currentSummary}`;
327551
327570
  for (const o of outcomes)
327552
327571
  (0, exploration_state_js_1.reduceEvent)(state2, { type: "ACTION_OUTCOME_CAPTURED", id: o.id, signal: o.signal });
327553
327572
  prevRouteSignatures.set(capRoute, currentSigs);
327554
- prevInteractedByRoute.set(capRoute, new Set(cumClicked));
327573
+ prevInteractionCountsByRoute.set(capRoute, new Map(curCounts));
327555
327574
  previousCaptureRoute = capRoute;
327575
+ previousCaptureTransitionCount = state2.collectedTransitions.length;
327556
327576
  }
327557
327577
  {
327558
327578
  const pendingScreenshot = consumePendingRouteScreenshot(pendingRouteScreenshot, {
@@ -328109,7 +328129,7 @@ finish_exploration bounced (primary-action ${finishRedirects}/${survey_link_fron
328109
328129
  content: JSON.stringify({
328110
328130
  acknowledged: false,
328111
328131
  keepExploring: true,
328112
- reason: `Not done yet \u2014 ${actionDebt.length} primary action(s) for this feature were never exercised to an observed result: ${targets}. Click each, wait for it to complete, then capture_page the result state (a new panel, row, status, or route change). If one is genuinely blocked (needs a paid plan, a real recipient, or a permission you don't have), say so in your summary and finish.`
328132
+ reason: `Not done yet \u2014 ${actionDebt.length} primary action(s) for this feature were never exercised to an observed result: ${targets}. This is a provisioned TEST account: click each, wait for it to complete (an async job can take 10-60s), then capture_page the result state (a new panel, row, score, status, or route change). Spending in-app compute/AI credits once is expected \u2014 not a blocker. Only if one is genuinely blocked \u2014 it charges REAL money at a checkout, makes an irreversible/destructive change or a production deployment, needs a real recipient, or needs a permission you don't have \u2014 say so in your summary and finish.`
328113
328133
  })
328114
328134
  });
328115
328135
  if (!finishStreakCountedThisBatch) {
@@ -328133,7 +328153,7 @@ finish_exploration bounced (primary-action ${finishRedirects}/${survey_link_fron
328133
328153
  logs2.push(`
328134
328154
  Exploration complete: ${summary}`);
328135
328155
  if (primaryActionLedgerActive) {
328136
- logs2.push(` [primary-action-ledger] mode=${primaryActionMode}: ${(0, primary_action_ledger_js_1.summarizeLedger)(state2.actionLedger)}`);
328156
+ logs2.push(` [primary-action-ledger] mode=${primaryActionMode}: ${(0, primary_action_ledger_js_1.summarizeLedger)(state2.actionLedger)}${state2.actionLedgerDropped > 0 ? ` ledger_cap_hits=${state2.actionLedgerDropped}` : ""}`);
328137
328157
  }
328138
328158
  toolResults.push({
328139
328159
  role: "tool",
@@ -353014,7 +353034,7 @@ var require_package4 = __commonJS({
353014
353034
  "package.json"(exports2, module2) {
353015
353035
  module2.exports = {
353016
353036
  name: "@validate.qa/runner",
353017
- version: "1.0.74",
353037
+ version: "1.0.76",
353018
353038
  description: "validate local test runner - execute Playwright tests on your machine, connected to validate.qa cloud.",
353019
353039
  bin: {
353020
353040
  "validate-runner": "dist/cli.js"
package/dist/cli.mjs CHANGED
@@ -320975,6 +320975,7 @@ var require_primary_action_ledger = __commonJS({
320975
320975
  exports.getBlockingActionDebt = getBlockingActionDebt;
320976
320976
  exports.identityLabelForKey = identityLabelForKey;
320977
320977
  exports.entryMatchesClick = entryMatchesClick;
320978
+ exports.computeNewlyClicked = computeNewlyClicked;
320978
320979
  exports.reconcileCapture = reconcileCapture;
320979
320980
  exports.classifyActionOutcome = classifyActionOutcome;
320980
320981
  exports.detectPrimaryActions = detectPrimaryActions;
@@ -321002,7 +321003,7 @@ var require_primary_action_ledger = __commonJS({
321002
321003
  exports.AMBIGUOUS_ACTION_VERB_RE = /^(continue|next|apply|go|proceed|confirm|update|add|edit|ok|done|finish|complete|approve|enable|activate)\b/i;
321003
321004
  exports.DESTRUCTIVE_ACTION_VERB_RE = /^(delete|remove|archive|reset|disconnect|deactivate|destroy|purge|revoke|discard|drop|wipe|erase|unsubscribe|cancel|clear|trash)\b/i;
321004
321005
  exports.PAID_ACTION_RE = /\b(pay|payment|checkout|check\s*out|subscribe|subscription|purchase|buy|upgrade|billing|(?:submit|place|confirm|complete|review)\s+(?:order|purchase|payment|booking)|(?:start|activate|begin|choose|select|try)\s+(?:a\s+|your\s+|the\s+)?(?:free\s+)?(?:subscription|trial|plan|membership|billing)|free\s+trial|send\s+(?:money|funds|cash|a\s+payment|payment|a\s+gift|gift)|order\s*now|donate|tip|renew|add\s*funds|add\s+to\s+cart|withdraw|deposit|transfer\s+(?:funds|money))\b/i;
321005
- exports.PROD_SIDE_EFFECT_RE = /\b(deploy|publish|push|ship|release|promote|roll\s*back|rollback)\b[^.]*\bto\b[^.]*\b(prod|production|live|mainnet)\b/i;
321006
+ exports.PROD_SIDE_EFFECT_RE = /\b(deploy|publish|push|ship|release|promote|roll\s*back|rollback)\b[^.\n;:,—–]{0,40}\bto\b[^.\n;:,—–]{0,40}\b(prod|production|live|mainnet)\b/i;
321006
321007
  exports.MESSAGE_PEOPLE_RE = /\b(invite|(?:send|resend)\s+(?:a\s+|an\s+|the\s+|new\s+|your\s+)?(?:invitation|invite|email|message|sms|text|reminder|notification|note|campaign|newsletter|broadcast|blast|bulk\s+email)|(?:send|email|message|text|notify)\b[^.]*\bto\s+(?:all|everyone|subscribers|the\s+list|your\s+list|contacts|customers|the\s+team)\b|share\s*via|notify\s*(?:team|user|member|people|everyone)|(?:email|message|text)\s+(?:the|a|all|your|team|user|customer|member))\b/i;
321007
321008
  function leadingVerb(label) {
321008
321009
  if (!label)
@@ -321108,10 +321109,13 @@ var require_primary_action_ledger = __commonJS({
321108
321109
  function normLabelKey(label) {
321109
321110
  return label.trim().toLowerCase().replace(/\s+/g, " ");
321110
321111
  }
321112
+ function stableLabelStem(label) {
321113
+ return normLabelKey(label).replace(/\s*\(\d{1,3}\)\s*$/, "").replace(/(?:\s*\.\.\.|…)\s*$/, "").replace(/\s+/g, " ").trim();
321114
+ }
321111
321115
  function identityLabelForKey(containerScope, label) {
321112
321116
  if (containerScope && GRID_SCOPE_RE.test(containerScope))
321113
321117
  return "";
321114
- return normLabelKey(label);
321118
+ return stableLabelStem(label);
321115
321119
  }
321116
321120
  function entryMatchesClick(entry, clickedLabels) {
321117
321121
  if (clickedLabels.has(entry.label))
@@ -321123,6 +321127,13 @@ var require_primary_action_ledger = __commonJS({
321123
321127
  }
321124
321128
  return false;
321125
321129
  }
321130
+ function computeNewlyClicked(prevCounts, curCounts) {
321131
+ const out = /* @__PURE__ */ new Set();
321132
+ for (const [label, n] of curCounts)
321133
+ if (n > (prevCounts.get(label) ?? 0))
321134
+ out.add(label);
321135
+ return out;
321136
+ }
321126
321137
  function reconcileCapture(input) {
321127
321138
  const nCur = normalizeActionRoute(input.route);
321128
321139
  const nPrev = input.previousRoute ? normalizeActionRoute(input.previousRoute) : null;
@@ -321138,36 +321149,38 @@ var require_primary_action_ledger = __commonJS({
321138
321149
  }
321139
321150
  }
321140
321151
  if (input.prevSignatures) {
321141
- const activeThisInterval = [];
321152
+ const matching = [];
321142
321153
  for (const e of input.ledger.values()) {
321143
321154
  if (e.kind !== "primary" || e.isExempt)
321144
321155
  continue;
321145
- if (e.status === "OUTCOME_CAPTURED" || e.status === "BLOCKED")
321146
- continue;
321147
321156
  if (normalizeActionRoute(e.route) !== nCur)
321148
321157
  continue;
321149
321158
  if (entryMatchesClick(e, input.newlyClickedThisRoute))
321150
- activeThisInterval.push(e.id);
321159
+ matching.push(e);
321151
321160
  }
321152
- if (activeThisInterval.length === 1) {
321161
+ const active = matching.filter((e) => e.status !== "OUTCOME_CAPTURED" && e.status !== "BLOCKED");
321162
+ if (matching.length === 1 && active.length === 1) {
321153
321163
  const revealed = (0, reveal_diff_js_1.computeRevealedControls)(input.prevSignatures, input.currentSignatures);
321154
321164
  if (revealed.size > 0)
321155
- outcomes.push({ id: activeThisInterval[0], signal: `revealed:${revealed.size}` });
321165
+ outcomes.push({ id: active[0].id, signal: `revealed:${revealed.size}` });
321156
321166
  }
321157
321167
  }
321158
321168
  if (nPrev && nPrev !== nCur && input.transitionTriggerLabels.size > 0) {
321169
+ const matching = [];
321159
321170
  for (const e of input.ledger.values()) {
321160
321171
  if (e.kind !== "primary" || e.isExempt)
321161
321172
  continue;
321162
- if (e.status === "OUTCOME_CAPTURED" || e.status === "BLOCKED")
321163
- continue;
321164
321173
  if (normalizeActionRoute(e.route) !== nPrev)
321165
321174
  continue;
321166
- if (entryMatchesClick(e, input.transitionTriggerLabels)) {
321167
- if (e.status === "DETECTED")
321168
- attempts.push(e.id);
321169
- outcomes.push({ id: e.id, signal: "route-change" });
321170
- }
321175
+ if (entryMatchesClick(e, input.transitionTriggerLabels))
321176
+ matching.push(e);
321177
+ }
321178
+ const active = matching.filter((e) => e.status !== "OUTCOME_CAPTURED" && e.status !== "BLOCKED");
321179
+ if (matching.length === 1 && active.length === 1) {
321180
+ const e = active[0];
321181
+ if (e.status === "DETECTED")
321182
+ attempts.push(e.id);
321183
+ outcomes.push({ id: e.id, signal: "route-change" });
321171
321184
  }
321172
321185
  }
321173
321186
  return { attempts, outcomes };
@@ -321286,6 +321299,7 @@ var require_exploration_state = __commonJS({
321286
321299
  testedRoutes: /* @__PURE__ */ new Set(),
321287
321300
  routeState: /* @__PURE__ */ new Map(),
321288
321301
  actionLedger: /* @__PURE__ */ new Map(),
321302
+ actionLedgerDropped: 0,
321289
321303
  frontier: /* @__PURE__ */ new Map(),
321290
321304
  recordedJourneys: [],
321291
321305
  suggestCommandCursors: /* @__PURE__ */ new Map(),
@@ -321326,6 +321340,7 @@ var require_exploration_state = __commonJS({
321326
321340
  recentProgressSignals: [],
321327
321341
  lastMeaningfulAction: null,
321328
321342
  interactedElements: /* @__PURE__ */ new Set(),
321343
+ interactionCounts: /* @__PURE__ */ new Map(),
321329
321344
  discoveredSubNav: [],
321330
321345
  exploredSubNav: /* @__PURE__ */ new Set(),
321331
321346
  failedActions: [],
@@ -321430,6 +321445,7 @@ var require_exploration_state = __commonJS({
321430
321445
  if (nameMatch) {
321431
321446
  const label = nameMatch[1];
321432
321447
  rs.interactedElements.add(label);
321448
+ rs.interactionCounts.set(label, (rs.interactionCounts.get(label) ?? 0) + 1);
321433
321449
  if (rs.discoveredSubNav.some((s) => s.label === label)) {
321434
321450
  rs.exploredSubNav.add(label);
321435
321451
  }
@@ -321472,8 +321488,10 @@ var require_exploration_state = __commonJS({
321472
321488
  const key = (0, primary_action_ledger_js_1.primaryActionKey)(event.route, c.verb, c.role, a.containerScope, c.kind, c.exemptReason, a.identityLabel);
321473
321489
  const existing = state2.actionLedger.get(key);
321474
321490
  if (!existing) {
321475
- if (state2.actionLedger.size >= LEDGER_CAP)
321491
+ if (state2.actionLedger.size >= LEDGER_CAP) {
321492
+ state2.actionLedgerDropped++;
321476
321493
  continue;
321494
+ }
321477
321495
  const entry = (0, primary_action_ledger_js_1.makeLedgerEntry)(key, c, event.route, a.containerScope, state2.iteration);
321478
321496
  entry.collapsed = Math.max(1, a.count);
321479
321497
  state2.actionLedger.set(key, entry);
@@ -322729,7 +322747,8 @@ var require_exploration_prompt_builder = __commonJS({
322729
322747
  const out = [
322730
322748
  "",
322731
322749
  "### Primary Actions Not Yet Exercised",
322732
- "These are the feature's core actions. Exercise each SAFE one: click it, wait for it to finish, then capture_page the result (a new panel, row, status, or route change). Do NOT click anything that would spend money, message real people, deploy to production, or permanently delete data \u2014 note it as blocked and move on. Don't finish until each safe action has produced an observed outcome:"
322750
+ "These are the feature's core actions \u2014 what it EXISTS to do. You are on a PROVISIONED TEST ACCOUNT, so exercising them is the point of this exploration. For each: click it, wait for it to finish (an async job can take 10-60s \u2014 keep waiting and capturing, do not give up early), then capture_page the result (a new panel, row, score, status, or route change).",
322751
+ `Spending in-app COMPUTE credits is expected and budgeted \u2014 running an Analyze / Generate / Run / Process ONCE to observe its output is exactly what you should do; an "N of M used" usage or AI-credit meter is NOT a reason to skip. These were auto-screened, but YOU are the final check: if you can see a specific listed action actually charges REAL money at a checkout (Buy / Upgrade / Subscribe), makes an irreversible or destructive change (Delete / Wipe / Deploy to production), or sends a message to real people (Send email / Invite), SKIP just that one \u2014 note it as blocked with the reason and move on. Don't finish until every other action below has produced an observed outcome:`
322733
322752
  ];
322734
322753
  for (const e of debt.slice(0, 8)) {
322735
322754
  const note = e.status === "ATTEMPTED" ? " \u2014 clicked but no result captured yet" : "";
@@ -326552,8 +326571,9 @@ ${inbox_tools_js_1.EMAIL_VERIFICATION_UNAVAILABLE_OVERRIDE_BLOCK}` : ""}`;
326552
326571
  const primaryActionLedgerActive = (0, primary_action_ledger_js_1.isLedgerActive)(primaryActionMode) && options?.mode === "deep";
326553
326572
  const primaryActionEnforcing = primaryActionLedgerActive && (0, primary_action_ledger_js_1.isGateEnforcing)(primaryActionMode);
326554
326573
  const prevRouteSignatures = /* @__PURE__ */ new Map();
326555
- const prevInteractedByRoute = /* @__PURE__ */ new Map();
326574
+ const prevInteractionCountsByRoute = /* @__PURE__ */ new Map();
326556
326575
  let previousCaptureRoute = null;
326576
+ let previousCaptureTransitionCount = 0;
326557
326577
  const linkScope = (0, survey_link_frontier_js_1.deriveLinkScope)(baseUrl);
326558
326578
  const surveyNavScope = options?.mode === "survey" ? (0, survey_scope_js_1.deriveSurveyPathScope)(baseUrl) : null;
326559
326579
  let finishRedirects = 0;
@@ -327524,16 +327544,15 @@ ${currentSummary}`;
327524
327544
  }
327525
327545
  const currentSigs = (0, reveal_diff_js_1.buildSnapshotSignatures)(capElements);
327526
327546
  const cumClicked = state2.routeState.get(capRoute)?.interactedElements ?? /* @__PURE__ */ new Set();
327527
- const prevClicked = prevInteractedByRoute.get(capRoute) ?? /* @__PURE__ */ new Set();
327528
- const newlyClicked = /* @__PURE__ */ new Set();
327529
- for (const l of cumClicked)
327530
- if (!prevClicked.has(l))
327531
- newlyClicked.add(l);
327547
+ const curCounts = state2.routeState.get(capRoute)?.interactionCounts ?? /* @__PURE__ */ new Map();
327548
+ const prevCounts = prevInteractionCountsByRoute.get(capRoute) ?? /* @__PURE__ */ new Map();
327549
+ const newlyClicked = (0, primary_action_ledger_js_1.computeNewlyClicked)(prevCounts, curCounts);
327532
327550
  const transitionTriggerLabels = /* @__PURE__ */ new Set();
327533
327551
  if (previousCaptureRoute && (0, exploration_parser_js_1.normalizeRouteForFocusScope)(previousCaptureRoute) !== (0, exploration_parser_js_1.normalizeRouteForFocusScope)(capRoute)) {
327534
327552
  const nPrevCap = (0, exploration_parser_js_1.normalizeRouteForFocusScope)(previousCaptureRoute);
327535
327553
  const nCap = (0, exploration_parser_js_1.normalizeRouteForFocusScope)(capRoute);
327536
- for (const t of state2.collectedTransitions) {
327554
+ for (let ti = previousCaptureTransitionCount; ti < state2.collectedTransitions.length; ti++) {
327555
+ const t = state2.collectedTransitions[ti];
327537
327556
  if (!t.triggerLabel)
327538
327557
  continue;
327539
327558
  if ((0, exploration_parser_js_1.normalizeRouteForFocusScope)(t.fromRoute) === nPrevCap && (0, exploration_parser_js_1.normalizeRouteForFocusScope)(t.toRoute) === nCap) {
@@ -327556,8 +327575,9 @@ ${currentSummary}`;
327556
327575
  for (const o of outcomes)
327557
327576
  (0, exploration_state_js_1.reduceEvent)(state2, { type: "ACTION_OUTCOME_CAPTURED", id: o.id, signal: o.signal });
327558
327577
  prevRouteSignatures.set(capRoute, currentSigs);
327559
- prevInteractedByRoute.set(capRoute, new Set(cumClicked));
327578
+ prevInteractionCountsByRoute.set(capRoute, new Map(curCounts));
327560
327579
  previousCaptureRoute = capRoute;
327580
+ previousCaptureTransitionCount = state2.collectedTransitions.length;
327561
327581
  }
327562
327582
  {
327563
327583
  const pendingScreenshot = consumePendingRouteScreenshot(pendingRouteScreenshot, {
@@ -328114,7 +328134,7 @@ finish_exploration bounced (primary-action ${finishRedirects}/${survey_link_fron
328114
328134
  content: JSON.stringify({
328115
328135
  acknowledged: false,
328116
328136
  keepExploring: true,
328117
- reason: `Not done yet \u2014 ${actionDebt.length} primary action(s) for this feature were never exercised to an observed result: ${targets}. Click each, wait for it to complete, then capture_page the result state (a new panel, row, status, or route change). If one is genuinely blocked (needs a paid plan, a real recipient, or a permission you don't have), say so in your summary and finish.`
328137
+ reason: `Not done yet \u2014 ${actionDebt.length} primary action(s) for this feature were never exercised to an observed result: ${targets}. This is a provisioned TEST account: click each, wait for it to complete (an async job can take 10-60s), then capture_page the result state (a new panel, row, score, status, or route change). Spending in-app compute/AI credits once is expected \u2014 not a blocker. Only if one is genuinely blocked \u2014 it charges REAL money at a checkout, makes an irreversible/destructive change or a production deployment, needs a real recipient, or needs a permission you don't have \u2014 say so in your summary and finish.`
328118
328138
  })
328119
328139
  });
328120
328140
  if (!finishStreakCountedThisBatch) {
@@ -328138,7 +328158,7 @@ finish_exploration bounced (primary-action ${finishRedirects}/${survey_link_fron
328138
328158
  logs2.push(`
328139
328159
  Exploration complete: ${summary}`);
328140
328160
  if (primaryActionLedgerActive) {
328141
- logs2.push(` [primary-action-ledger] mode=${primaryActionMode}: ${(0, primary_action_ledger_js_1.summarizeLedger)(state2.actionLedger)}`);
328161
+ logs2.push(` [primary-action-ledger] mode=${primaryActionMode}: ${(0, primary_action_ledger_js_1.summarizeLedger)(state2.actionLedger)}${state2.actionLedgerDropped > 0 ? ` ledger_cap_hits=${state2.actionLedgerDropped}` : ""}`);
328142
328162
  }
328143
328163
  toolResults.push({
328144
328164
  role: "tool",
@@ -353019,7 +353039,7 @@ var require_package4 = __commonJS({
353019
353039
  "package.json"(exports, module) {
353020
353040
  module.exports = {
353021
353041
  name: "@validate.qa/runner",
353022
- version: "1.0.74",
353042
+ version: "1.0.76",
353023
353043
  description: "validate local test runner - execute Playwright tests on your machine, connected to validate.qa cloud.",
353024
353044
  bin: {
353025
353045
  "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.74",
3
+ "version": "1.0.76",
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"