@keepsnappy/agent 0.1.0-alpha.20 → 0.1.0-alpha.21

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.
@@ -1030,7 +1030,7 @@ import { dirname, join, relative, resolve } from "node:path";
1030
1030
  import { fileURLToPath } from "node:url";
1031
1031
  var DRIVER_NAME = "appium";
1032
1032
  function driverVersion() {
1033
- const built = "0.1.0-alpha.20";
1033
+ const built = "0.1.0-alpha.21";
1034
1034
  if (built)
1035
1035
  return built;
1036
1036
  let dir = dirname(fileURLToPath(import.meta.url));
@@ -2334,7 +2334,7 @@ function withLaunchNames(windows) {
2334
2334
  }
2335
2335
 
2336
2336
  // packages/derive/dist/src/model.js
2337
- var DERIVATION_VERSION = 28;
2337
+ var DERIVATION_VERSION = 29;
2338
2338
  // packages/stats/dist/src/core/rng.js
2339
2339
  function splitmix32(seed) {
2340
2340
  let s = seed >>> 0;
@@ -5006,7 +5006,7 @@ function keyFirstOpens(windows) {
5006
5006
  }
5007
5007
 
5008
5008
  // packages/derive/dist/src/derive.js
5009
- var DERIVATION_VERSIONS = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 26, 27, 28];
5009
+ var DERIVATION_VERSIONS = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 26, 27, 28, 29];
5010
5010
  var windowsForVersion = (windows, version = DERIVATION_VERSION) => version >= 22 ? keyFirstOpens(windows) : windows;
5011
5011
  function stepWindowsFor(windows, version = DERIVATION_VERSION) {
5012
5012
  return windowsForVersion(version >= 16 ? withLaunchNames(windows).filter((w) => !isBackgroundWindow(w)) : windows, version);
@@ -5332,6 +5332,17 @@ function valueText(measure, v) {
5332
5332
  const def = defOf(measure);
5333
5333
  return format(def.unit, def.decimals, v);
5334
5334
  }
5335
+ function rangeText(measure, lo, hi) {
5336
+ const a = valueText(measure, lo);
5337
+ const b = valueText(measure, hi);
5338
+ if (a === b)
5339
+ return a;
5340
+ const unitOf = (text) => text.slice(text.lastIndexOf(" ") + 1);
5341
+ const shared = a.includes(" ") && b.includes(" ") && unitOf(a) === unitOf(b);
5342
+ if (shared)
5343
+ return `${a.slice(0, a.lastIndexOf(" "))}–${b}`;
5344
+ return a.includes(" ") || b.includes(" ") ? `${a} – ${b}` : `${a}–${b}`;
5345
+ }
5335
5346
  var KEY_SEPARATOR = ":";
5336
5347
  function breakdownMeasure(field) {
5337
5348
  const at = field.indexOf(KEY_SEPARATOR);
@@ -5609,9 +5620,15 @@ var atMost = (text, isBound) => isBound ? `${AT_MOST}${text}` : text;
5609
5620
  var AT_LEAST = "≥ ";
5610
5621
  var atLeast = (text, isBound) => isBound ? `${AT_LEAST}${text}` : text;
5611
5622
 
5623
+ // packages/present/dist/src/place-tier.js
5624
+ var slowerCountsText = (n, sep = " · ") => [n.worse ? `${n.worse} ${STATE_WORDS.worse.toLowerCase()}` : "", n.slight ? `${n.slight} ${STATE_WORDS.warning.toLowerCase()}` : ""].filter(Boolean).join(sep);
5625
+
5612
5626
  // packages/present/dist/src/shown.js
5613
- var feltGainText = (ms) => ms && ms > 0 ? `felt gain −${fieldValueText("jank_ms", ms)}` : null;
5614
- var feltCostText = (ms) => ms && ms > 0 ? `felt cost +${fieldValueText("jank_ms", ms)}` : null;
5627
+ function feltRangeText(ms, byMs, direction) {
5628
+ if (!ms || !(ms > 0))
5629
+ return null;
5630
+ return `${rangeText("jank_ms", ms, byMs !== null && byMs !== undefined && byMs - ms >= 1 ? byMs : ms)} ${direction}`;
5631
+ }
5615
5632
 
5616
5633
  // packages/present/dist/src/stability.js
5617
5634
  var UNSTABLE_WORDS = {
@@ -5808,8 +5825,12 @@ var PROBLEMS_BLOCK_WORDS = {
5808
5825
  filter: "Which problems"
5809
5826
  }
5810
5827
  };
5811
- // packages/present/dist/src/place-tier.js
5812
- var slowerCountsText = (n, sep = " · ") => [n.worse ? `${n.worse} ${STATE_WORDS.worse.toLowerCase()}` : "", n.slight ? `${n.slight} ${STATE_WORDS.warning.toLowerCase()}` : ""].filter(Boolean).join(sep);
5828
+ // packages/present/dist/src/tip.js
5829
+ function tip(headline, rows = [], foot) {
5830
+ const made = rows.flatMap((r) => r ? [Array.isArray(r) ? { label: r[0], value: r[1] } : r] : []);
5831
+ return foot ? { headline, rows: made, foot } : { headline, rows: made };
5832
+ }
5833
+
5813
5834
  // packages/present/dist/src/verdict-section.js
5814
5835
  var places2 = (n) => `${n.toLocaleString("en-US")} ${n === 1 ? "place" : "places"}`;
5815
5836
  var of = (n, total) => `${n.toLocaleString("en-US")} of ${places2(total)}`;
@@ -5966,10 +5987,38 @@ var BY_RANK = Object.keys(MAIN_TIER_RANK).sort((a, b) => MAIN_TIER_RANK[a] - MAI
5966
5987
  var MEMORY_HELD_WORDS = { head: "Memory", rest: "still held after the journey", text: "Memory still held after the journey" };
5967
5988
  var MAIN_CHART_TITLES = {
5968
5989
  renders: "Places vs their normal range",
5969
- rendersTip: (baseline) => `Share of the journey’s places per session, by result against main’s ${baseline} sessions before it. Dashed: not judged yet, while main collects ${baseline} sessions on the setup (after main’s start or a change).`,
5990
+ rendersTip: (baseline) => tip("Places vs their normal range", [
5991
+ ["Bar", "The journey’s places in one session"],
5992
+ ["Colour", `Result against main’s ${baseline} sessions before it`],
5993
+ ["Dashed", `Not judged yet: main is collecting ${baseline} sessions on this setup`]
5994
+ ], "Main collects them after its start or a change"),
5970
5995
  memory: `${MEMORY_HELD_WORDS.text} · MB`,
5971
- memoryTip: "Memory kept by the journey: what it still holds after the clean-up at its end. Line: the median of a session; band: the spread inside it."
5996
+ memoryTip: tip(MEASURES["mem.retained_growth_mb"].label, [
5997
+ ["Shows", "What it still holds after the clean-up at its end"],
5998
+ ["Line", "The median of a session"],
5999
+ ["Band", "The spread inside the session"]
6000
+ ])
6001
+ };
6002
+
6003
+ // packages/present/dist/src/tips.js
6004
+ var LEAD_TIP = tip("A lead, not a confirmed cause", [], "Read from what the app did");
6005
+ // packages/present/dist/src/rules-lines.js
6006
+ var RULES_WORDS = {
6007
+ action: "Recompute",
6008
+ older: "Computed with older rules",
6009
+ marker: "new rules",
6010
+ blocksUnderPrevious: "blocks under the previous rules",
6011
+ bannerText: "New rules: pull requests wait for a recompute",
6012
+ bannerAction: "Recompute",
6013
+ engine: "engine",
6014
+ engineUpdate: "engine update"
5972
6015
  };
6016
+ var sameRulesRef = (a, b) => a.configId === b.configId && a.coreVersion === b.coreVersion && a.derivation === b.derivation && (a.configFingerprint === undefined || b.configFingerprint === undefined || a.configFingerprint === b.configFingerprint);
6017
+ var rulesChangeText = (computed, live) => sameRulesRef(computed, live) ? null : RULES_WORDS.older;
6018
+ var staleRulesText = (rules) => rules ? rulesChangeText(rules.computed, rules.live) : null;
6019
+ var blocksUnderPreviousRulesText = (r) => r.blocking && r.stale ? RULES_WORDS.blocksUnderPrevious : null;
6020
+ var waitingForRecomputeBanner = (r) => r.waiting > 0 ? `${RULES_WORDS.bannerText} · ${RULES_WORDS.bannerAction}` : null;
6021
+
5973
6022
  // packages/present/dist/src/summary-message.js
5974
6023
  var SUMMARY_WORDS = {
5975
6024
  notComputed: "Not judged yet",
@@ -6000,10 +6049,12 @@ var SUMMARY_WORDS = {
6000
6049
  link: {
6001
6050
  comparison: "See the comparison",
6002
6051
  session: "See the session",
6052
+ mainOverTime: "See main over time",
6003
6053
  branch: (branch) => `Open ${branch}`,
6004
6054
  runSet: "Open the run set"
6005
6055
  }
6006
6056
  };
6057
+ var feltOf = (l) => l.state === "worse" || l.state === "warning" ? feltRangeText(l.feltCostMs, l.slowerByMs, "slower") : l.state === "better" ? feltRangeText(l.feltGainMs, l.fasterByMs, "faster") : null;
6007
6058
  var ORDER = [...STATE_ORDER.slice(0, 3), "not-computed", ...STATE_ORDER.slice(3)];
6008
6059
  var rank = (l) => ORDER.indexOf(l.state);
6009
6060
  var WORD = { ...STATE_WORDS, "not-computed": SUMMARY_WORDS.notComputed };
@@ -6067,18 +6118,21 @@ var noneMoved = VERDICT_SECTION_WORDS.noneMoved(1).replace(/ \(.*\)$/, "");
6067
6118
  function conditions(l, pr) {
6068
6119
  const moved = l.state === "worse" || l.state === "warning";
6069
6120
  return [
6070
- pr && l.state === "worse" ? l.blocking ? VERDICT_SECTION_WORDS.blocksMerge : NOT_BLOCKING : "",
6121
+ pr && l.state === "worse" ? l.blocking ? blocksUnderPreviousRulesText({ blocking: true, stale: !!l.rules }) ?? VERDICT_SECTION_WORDS.blocksMerge : NOT_BLOCKING : "",
6071
6122
  moved && l.measuredDifferently ? SUMMARY_WORDS.measuredDifferently : "",
6072
6123
  (moved || l.state === "better") && l.fewRuns ? SUMMARY_WORDS.fewRuns : ""
6073
6124
  ].filter(Boolean);
6074
6125
  }
6126
+ var rulesLines = (l) => [staleRulesText(l.rules), waitingForRecomputeBanner({ waiting: l.waiting ?? 0 })].filter((x) => x !== null);
6127
+ function comparedText(main, kind) {
6128
+ const args = [SUMMARY_WORDS.sessions(main.sessions), SUMMARY_WORDS.days(days(main.labels))];
6129
+ return `${SUMMARY_WORDS.compared[kind](...args)} · ${SUMMARY_WORDS.runs(main.runs, main.each)}`;
6130
+ }
6075
6131
  function compared(l, pr, slower) {
6076
6132
  const judged = l.state === "worse" || l.state === "warning" || l.state === "better" || l.state === "no-change";
6077
6133
  if (!judged || !l.main?.sessions)
6078
6134
  return null;
6079
- const args = [SUMMARY_WORDS.sessions(l.main.sessions), SUMMARY_WORDS.days(days(l.main.labels))];
6080
- const what = pr ? SUMMARY_WORDS.compared.branch(...args) : slower ? SUMMARY_WORDS.compared.mainSlower(...args) : SUMMARY_WORDS.compared.main(...args);
6081
- return `${what} · ${SUMMARY_WORDS.runs(l.main.runs, l.main.each)}`;
6135
+ return comparedText(l.main, pr ? "branch" : slower ? "mainSlower" : "main");
6082
6136
  }
6083
6137
  function summaryMessage(input) {
6084
6138
  const l = shownLane(input.lanes);
@@ -6109,14 +6163,14 @@ function summaryMessage(input) {
6109
6163
  const lines = [];
6110
6164
  if (l.state === "worse" || l.state === "warning") {
6111
6165
  const n = l.places || l.changes.length;
6112
- lines.push(upper([...n ? [SUMMARY_WORDS.slowerPlaces(n, tierSplit(l))] : [], feltCostText(l.feltCostMs)].filter(Boolean).join(" · ") || why(l)));
6166
+ lines.push(upper([...n ? [SUMMARY_WORDS.slowerPlaces(n, tierSplit(l))] : [], feltOf(l)].filter(Boolean).join(" · ") || why(l)));
6113
6167
  } else if (l.state === "no-change")
6114
6168
  lines.push(upper(SUMMARY_WORDS.noPlaceSlower));
6115
6169
  else if (l.state === "cant-tell") {
6116
6170
  const fix = unblock(l, input.pr);
6117
6171
  lines.push(fix ? SUMMARY_WORDS.unblock(why(l), fix) : why(l));
6118
6172
  } else if (l.state === "better")
6119
- lines.push([why(l), feltGainText(l.feltGainMs)].filter(Boolean).join(" · "));
6173
+ lines.push([why(l), feltOf(l)].filter(Boolean).join(" · "));
6120
6174
  else
6121
6175
  lines.push(why(l));
6122
6176
  if (l.toCheckCount)
@@ -6127,6 +6181,7 @@ function summaryMessage(input) {
6127
6181
  const context = [basis ? `${basis}${extra ? `; ${extra}` : ""}` : extra, heat].filter(Boolean).join(" · ");
6128
6182
  if (context)
6129
6183
  lines.push(basis ? context : upper(context));
6184
+ lines.push(...rulesLines(l));
6130
6185
  return { state: l.state, newestSlower, lead, ...withLines(lines), link };
6131
6186
  }
6132
6187
  function changeLines(l, pr, newestSlower) {
@@ -6134,9 +6189,9 @@ function changeLines(l, pr, newestSlower) {
6134
6189
  const basis = compared(l, pr, newestSlower);
6135
6190
  const extra = conditions(l, pr).join(", ");
6136
6191
  const heat = l.heatedRuns ? upper(SUMMARY_WORDS.heated(l.heatedRuns, l.runs)) : null;
6137
- const felt = l.state === "worse" || l.state === "warning" ? feltCostText(l.feltCostMs) : l.state === "better" ? feltGainText(l.feltGainMs) : null;
6192
+ const felt = feltOf(l);
6138
6193
  const sums = upper([felt, l.toCheckCount ? SUMMARY_WORDS.toCheck(l.toCheckCount) : null].filter(Boolean).join(" · "));
6139
- const tail = [...sums ? [sums] : [], ...basis || extra ? [basis ? `${basis}${extra ? `; ${extra}` : ""}` : upper(extra)] : [], ...heat ? [heat] : []];
6194
+ const tail = [...sums ? [sums] : [], ...basis || extra ? [basis ? `${basis}${extra ? `; ${extra}` : ""}` : upper(extra)] : [], ...heat ? [heat] : [], ...rulesLines(l)];
6140
6195
  if (l.state === "worse" || l.state === "warning" || l.state === "better") {
6141
6196
  const seen = new Set;
6142
6197
  const places = l.changes.filter((c) => {
@@ -6166,6 +6221,7 @@ function pendingMessage(branch, url) {
6166
6221
  function noResultMessage(branch, reason, url) {
6167
6222
  return { state: "not-computed", newestSlower: false, lead: `${SUMMARY_WORDS.noResult} · ${clip(branch, NAME.name)}`, ...withLines([reason.replace(/\.$/, "")]), link: url ? { label: SUMMARY_WORDS.link.runSet, url } : null };
6168
6223
  }
6224
+
6169
6225
  // packages/present/dist/src/run-set-message.js
6170
6226
  var midLine = (label, metric) => !metric.startsWith("component:") && /^[A-Z][a-z]*(\s|$)/.test(label) ? `${label[0].toLowerCase()}${label.slice(1)}` : label;
6171
6227
  function movedItems(v, sc) {
@@ -6257,7 +6313,11 @@ function laneOf(v, sc, set) {
6257
6313
  reason: sc.headline?.reason ?? sc.headline?.reasons?.[0] ?? null,
6258
6314
  feltCostMs: sc.headline?.felt_cost_ms ?? null,
6259
6315
  feltGainMs: sc.headline?.felt_gain_ms ?? null,
6260
- toCheckCount: sc.headline?.to_check_count ?? null
6316
+ slowerByMs: sc.headline?.slower_by_ms ?? null,
6317
+ fasterByMs: sc.headline?.faster_by_ms ?? null,
6318
+ toCheckCount: sc.headline?.to_check_count ?? null,
6319
+ rules: sc.headline?.rules ?? null,
6320
+ waiting: sc.headline?.waiting_for_reprocess ?? 0
6261
6321
  };
6262
6322
  }
6263
6323
  var isPending = (s) => lanesOf(s).every((l) => l.state === "not-computed" && !l.word?.startsWith(`${NOT_JUDGED_WORD}: `));
@@ -6296,14 +6356,8 @@ ${esc(m.detail)}` } },
6296
6356
  }]
6297
6357
  };
6298
6358
  }
6299
- function resultSlackPayload(s, o = {}) {
6300
- if (!s.verdicts)
6301
- return null;
6302
- const lanes = lanesOf(s);
6303
- const moved = lanes.some((l) => l.state === "worse" || l.state === "warning");
6304
- const heat = lanes.some((l) => l.heatedRuns > 0);
6305
- const improved = lanes.some((l) => l.state === "better") || s.verdicts.state === "better";
6306
- if (!isPr(s) && !moved && !improved && !heat && !o.always)
6359
+ function resultSlackPayload(s) {
6360
+ if (!s.verdicts || !isPr(s))
6307
6361
  return null;
6308
6362
  return slackPayload(runSetMessage(s, { slack: true }), messageTone(s));
6309
6363
  }
@@ -6318,6 +6372,7 @@ ${m.link ? `
6318
6372
  var resultMarkdown = (s) => markdown(runSetMessage(s), messageTone(s).mark);
6319
6373
  var noResultMarkdown = (branch, reason, url) => markdown(noResultMessage(branch, reason, url), "⚪");
6320
6374
  var pendingMarkdown = (branch, url) => markdown(pendingMessage(branch, url), "⚪");
6375
+
6321
6376
  // packages/present/dist/src/stale.js
6322
6377
  function staleNoteText(why) {
6323
6378
  const newer = why.includes("newer-run-set");
@@ -6335,6 +6390,15 @@ var staleFlagText = (why) => {
6335
6390
  return `${note.charAt(0).toLowerCase()}${note.slice(1)}`;
6336
6391
  };
6337
6392
  var STALE_MARK = "stale result";
6393
+
6394
+ // packages/present/dist/src/main-session-message.js
6395
+ var TONE = {
6396
+ worse: { mark: "\uD83D\uDD34", color: MESSAGE_COLORS.error },
6397
+ slight: { mark: "\uD83D\uDFE1", color: MESSAGE_COLORS.warn },
6398
+ better: { mark: "\uD83D\uDFE2", color: MESSAGE_COLORS.better },
6399
+ cant: { mark: "❔", color: MESSAGE_COLORS.none },
6400
+ same: { mark: "⚪", color: MESSAGE_COLORS.none }
6401
+ };
6338
6402
  // apps/agent/src/upload.ts
6339
6403
  import { join as join4 } from "node:path";
6340
6404
  var MAX_RATE_RETRIES = 5;
@@ -6494,7 +6558,8 @@ var addCounts = (xs) => xs.reduce((a, c) => ({ worse: a.worse + c.worse, warning
6494
6558
  var setupLine = (h) => {
6495
6559
  const parts = [
6496
6560
  h?.measured_differently?.length ? `Measured differently (${h.measured_differently.join(", ")}) — ${NOT_BLOCKING}` : "",
6497
- h?.waiting_for_reprocess ? waitingWords(h.waiting_for_reprocess) : ""
6561
+ h?.waiting_for_reprocess ? waitingWords(h.waiting_for_reprocess) : "",
6562
+ staleRulesText(h?.rules) ?? ""
6498
6563
  ].filter(Boolean);
6499
6564
  return parts.length ? parts.join("; ") : null;
6500
6565
  };
@@ -11008,7 +11073,7 @@ import { existsSync, readFileSync, statSync } from "node:fs";
11008
11073
  import { dirname as dirname2, join as join11, relative as relative2, resolve as resolve4 } from "node:path";
11009
11074
  import { fileURLToPath } from "node:url";
11010
11075
  function driverVersion() {
11011
- const built = "0.1.0-alpha.20";
11076
+ const built = "0.1.0-alpha.21";
11012
11077
  if (built)
11013
11078
  return built;
11014
11079
  let dir = dirname2(fileURLToPath(import.meta.url));
@@ -11064,13 +11129,14 @@ var FLUSH_MARGIN_MS = 30000;
11064
11129
  var FLUSH_WAIT_MS = SDK_FLUSH_WORST_CASE_MS + FLUSH_MARGIN_MS;
11065
11130
  var FLUSH_STATUSES = new Set(["ok", "error", "off"]);
11066
11131
  // packages/verdict-core/dist/src/version.generated.js
11067
- var CORE_FINGERPRINT = "1c8a7ed82956dae0";
11132
+ var CORE_FINGERPRINT = "1aef48c7adfd5630";
11068
11133
 
11069
11134
  // packages/verdict-core/dist/src/version.js
11070
11135
  var CORE_VERSION = parseInt(CORE_FINGERPRINT.slice(0, 7), 16);
11071
11136
  // packages/verdict-core/dist/src/window-count.js
11072
11137
  var WINDOW_DEPENDENT_JOURNEY_MEASURES = ["sampler.cpu_ms", "sampler.frames.longest_gap_ms"];
11073
11138
  var OFF = new WeakSet;
11139
+
11074
11140
  // packages/verdict-core/dist/src/clauses.js
11075
11141
  var row = (id, what, unit, cmp, ruling, uncalibrated, value) => ({ id, what, unit, cmp, ruling, uncalibrated, value });
11076
11142
  var NO_RULING = "no ruling: a starting value of the deciding config (L2 audit F-13)";
@@ -11124,6 +11190,12 @@ var CLAUSES = {
11124
11190
  "U-NOISY": row("U-NOISY", "A place is unstable on main above this ratio of spread to allowed, and stays so down to `below`; from this many main runs in this many sessions.", "ratio", ">", "REQ-MET-29, Arek 2026-10-18 ~12:00-13:20; thresholds provisional", false, { above: 1, below: 0.75, minMainRuns: 6, minSessions: 3 })
11125
11191
  };
11126
11192
  var CLAUSE_LIST = Object.values(CLAUSES);
11193
+
11194
+ // packages/verdict-core/dist/src/comp-confirm.js
11195
+ var COMPONENT_K99 = (() => {
11196
+ const f = CLAUSES["T-WARN-NOISE"].value.component;
11197
+ return { a: 0, b: f.c, power: 0.5, lift: f.k99 };
11198
+ })();
11127
11199
  // packages/verdict-core/dist/src/config.js
11128
11200
  var WINDOWS = ["screen", "press", "drag", "step", "other"];
11129
11201
  var STEP = ["step"];
@@ -11374,6 +11446,17 @@ var DRIVER_RELEASES = [
11374
11446
  ],
11375
11447
  uncalibrated: false,
11376
11448
  source: '#417 (plan 84 step 2.1, D1, appium-driver CHANGELOG "release alpha.20", 2026-10-27): one gesture definition for every list and screen scroll on both platforms, one touch held 150 ms over 60 % of the list frame (at least 250 dp or pt). Android list and screen flings 12 000 / 3000 dp/s -> that contact (a shorter coast, about 1000-1600 dp in place of about 7000, so fewer momentum frames and possibly one more gesture per page pass); iOS list drags 75 % (end) / 65 % (top) -> 60 %, iOS drawn-row (inverted list) drags 400 -> 150 ms; touches reaching a system zone move whole. Every other request, body and wait is pinned unchanged against alpha.19. The CHANGELOG states "changes Android and iOS list/scroll places; compare only within alpha.20 on both platforms", so both platforms are declared at drag, step and journey places (REQ-FARM-35): the journey holds the whole-run measures the judge reads (memory kept, memory at launch, end and peak, whole-run CPU, the longest freeze), and a shorter coast and one more gesture per page pass move them too.'
11449
+ },
11450
+ {
11451
+ version: "0.1.0-alpha.21",
11452
+ previous: "0.1.0-alpha.20",
11453
+ declares: [
11454
+ { affects: MEASURES_AT("drag"), places: ["drag"] },
11455
+ { affects: MEASURES_AT("step"), places: ["step"] },
11456
+ { affects: MEASURES_AT("journey"), places: ["journey"] }
11457
+ ],
11458
+ uncalibrated: false,
11459
+ source: `#491 (appium-driver CHANGELOG "0.1.0-alpha.21", 2026-10-29): in a paged scrollToEnd pass, when a page has not landed by the end of its 3 s wait and the before row is cut by (Android: touches) the list's bottom edge, the pass spends its one extra end gesture and waits again, where alpha.20 failed the run. Such a run sends one more end gesture, one row lookup and one frame read; every other request, body and wait is unchanged. That drag window, the step whose scroll steps contain it and the whole run (one more gesture and coast) can move, on both platforms, so drag, step and journey places are declared (REQ-FARM-35).`
11377
11460
  }
11378
11461
  ];
11379
11462
  var JOURNEY_RELEASES = {};
@@ -11534,8 +11617,20 @@ var NOISY_WIDEN = {
11534
11617
  uncalibrated: false,
11535
11618
  source: `Arek 2026-10-27 ~19:45 [ctl-arek-noisy-widen]: noisy places judged with a widened line, tunable down to the old exclusion (excludeAbove 1.0 / 0.75 with the within-session spread); amendment ~19:40 ("Szum z 3 sesji main powinniśmy liczyć tak samo jak A/A"): one main session against the others, between-session shift included, plus the PR's run-to-run spread; ctl 19:45: ×1, k never raised to pass; start position: never left out (ctl 22:20, the Pixel 7 no longer counts). Pack v4 replay against simple-4.27 without the Pixel 7: A/A false Worse 1 / 1 of 1070 splits, doses caught Reference night 166 / 166, Reference PR 93 / 93, farm iPhone ms 4863 -> 5012 of 6580 (Worse 3107 -> 3167), places left out 9.8 % -> 0 %. Footnote Pixel 7 (21 splits): A/A Worse 0 -> 4`
11536
11619
  };
11620
+ var COMP_CONFIRM = {
11621
+ maxComponents: 3,
11622
+ share: 0.6,
11623
+ uncalibrated: false,
11624
+ source: `Arek 2026-10-28 ~08:00 idea, ctl 08:10 "Zleć jako pomiar", k-sweep ctl 08:50, deciding ~11:00 "tak" [ctl-arek-comp-confirm]: a place's render sums many components, the component that caused a slowdown carries far less noise; form B (the confirmed components' summed excess past their own lines is the place's excess); k 3 and share 60 % from the k-sweep (docs/stats/2026-10-28-comp-confirm.md); gate: A/A vs simple-4.28, added false Worse 0 and added false Slightly worse at most 1 per 500 splits`
11625
+ };
11626
+ var RELIABLE = {
11627
+ minValidRuns: 3,
11628
+ warmStart: { androidRawFrom: 3, iosFrom: 2 },
11629
+ uncalibrated: false,
11630
+ source: 'Arek via ctl 2026-10-28 ~11:40, corrected ~11:55 ("fair and moderate are not an overheated phone") [ctl-arek-thermal-unreliable]: a run that started at Android SEVERE (3) or above, or iOS serious (2) or above, makes its session unreliable; the SDK maps SEVERE to serious, so both scales read alike. NONE / LIGHT / MODERATE and nominal / fair are normal: listed as started warm, never unreliable. [ctl-arek-three-runs]: a session counts only with 3 valid runs, on main and on the PR (no earlier ruling on partial PR sessions: G-MINRUNS had none). Background: giga2-scratch/noise-p9p-g71/findings.md (P9P false Worse from LIGHT starts stay; the G71 1-run main session no longer counts)'
11631
+ };
11537
11632
  var SHAPE = {
11538
- id: "simple-4.28+noise95-99",
11633
+ id: "simple-4.29+noise95-99",
11539
11634
  minRuns: 2,
11540
11635
  baseline: { sessions: 3, ...GUESS("Q4 / REQ-VER-33: main's newest 3 sessions against one PR session (the quieter setting of the plan 58 simulation)") },
11541
11636
  repeats: { kind: "every-pr-beyond-every-main" },
@@ -11647,6 +11742,8 @@ var SHAPE = {
11647
11742
  source: `${UNSTABLE_NOISY.source}; Arek 2026-10-26 ("A wygląda kusząco. Oddaj do impl.", ctl-arek-met29-own-step): the step is the place's own work kind's (entry and in-screen mount 10 ms, update 5 ms), the smallest only when the kind is unknown; ctl-arek-jank-time: noisiness still reads the slowest commit, so a place is unstable exactly when it was before jank time was judged`
11648
11743
  }],
11649
11744
  noisy: NOISY_WIDEN,
11745
+ compConfirm: COMP_CONFIRM,
11746
+ reliable: RELIABLE,
11650
11747
  judgeFrom: { mainSessions: 3, uncalibrated: false, source: 'Arek 2026-10-18 (REQ-VER-41): a phone is judged only when main has at least 3 sessions on that phone and journey; with fewer the result is "not enough main yet", not judged, not blocking, values shown. Replay (agents/release/replay, d22): 1 PR vs 2 main on the S23 night pool gave 13 z 60 false worse+warning, 1 vs 3 gave 0' },
11651
11748
  measuredWith: [
11652
11749
  { aspect: "renderer", affects: REACT_MEASURES },
@@ -11816,7 +11913,7 @@ async function detectSimulator(deps, udid) {
11816
11913
  function slackMessage(s, ctx) {
11817
11914
  if (ctx.blocked)
11818
11915
  return noResultSlackPayload(ctx.branch, ctx.blocked.reason, s ? linkOf(s) : null);
11819
- return s ? resultSlackPayload(s, { always: ctx.always }) : null;
11916
+ return s ? resultSlackPayload(s) : null;
11820
11917
  }
11821
11918
  async function postToSlack(url, payload, fetchImpl = fetch) {
11822
11919
  try {
@@ -13189,7 +13286,8 @@ var SHORT_FORM_NOTES = {
13189
13286
  window_history: `the runs older than the newest ${SHORT_POINTS}`,
13190
13287
  pr_status: "the per-journey dashboard links and the heat note",
13191
13288
  list_problems: `the work and component lines, the dashboard links and the problems after the first ${SHORT_PLACES}`,
13192
- compare_runs: `the component lines and the measures beyond each place's ${SHORT_COMPARE_MEASURES} times that moved most`
13289
+ compare_runs: `the component lines and the measures beyond each place's ${SHORT_COMPARE_MEASURES} times that moved most`,
13290
+ get_investigation: "the lines of the worse places, the components to check, the problems and the commits"
13193
13291
  };
13194
13292
  var andList = (xs) => xs.length < 2 ? xs[0] ?? "" : `${xs.slice(0, -1).join(", ")} and ${xs[xs.length - 1]}`;
13195
13293
 
@@ -13372,10 +13470,14 @@ function formatBranchStatus(s, opts = {}) {
13372
13470
  if (s.lanes.length)
13373
13471
  r.both("");
13374
13472
  for (const l of s.lanes) {
13375
- const shown = `${l.result === "blocks" ? `${l.word} (blocks)` : badgedWord(l.word, l.blocking)}${l.stale ? ` (${STALE_FLAG})` : ""}`;
13473
+ const blocks = blocksUnderPreviousRulesText({ blocking: l.blocking, stale: !!l.rules }) ?? "blocks";
13474
+ const shown = `${l.result === "blocks" ? `${l.word} (${blocks})` : badgedWord(l.word, l.blocking)}${l.stale && !l.rules ? ` (${STALE_FLAG})` : ""}`;
13376
13475
  const sha = l.commit?.sha?.slice(0, 7);
13377
13476
  r.both(` ${l.journey_title ?? l.journey} on ${l.phone_name}: ${shown}${feltWords(l)} · ${sha ? `commit ${sha}` : "commit not recorded"} (${when(l.at)})${l.in_newest_run_set ? "" : " · older run set"}${l.waiting_for_reprocess ? `; ${waitingWords(l.waiting_for_reprocess)}` : ""}`);
13378
13477
  r.both(...resultContextLines(l.context, opts.verbose, opts.form === "short"));
13478
+ const rulesLine = staleRulesText(l.rules);
13479
+ if (rulesLine)
13480
+ r.both(` ${rulesLine}`);
13379
13481
  r.both(...(l.comparison_notes ?? []).map((line) => ` ${line}`));
13380
13482
  r.full(...(l.session_speed ?? []).map((line) => ` ${line}`));
13381
13483
  for (const f of l.worst)
@@ -13804,7 +13906,7 @@ class ApiError extends Error {
13804
13906
  this.name = "ApiError";
13805
13907
  }
13806
13908
  }
13807
- async function get(t, path, query = {}) {
13909
+ async function get(t, path, query = {}, send = {}) {
13808
13910
  const u = new URL(path, t.url.endsWith("/") ? t.url : `${t.url}/`);
13809
13911
  for (const [k, v] of Object.entries(query)) {
13810
13912
  if (Array.isArray(v))
@@ -13813,7 +13915,10 @@ async function get(t, path, query = {}) {
13813
13915
  else if (v !== undefined && v !== "")
13814
13916
  u.searchParams.set(k, String(v));
13815
13917
  }
13816
- const r = await (t.fetch ?? fetch)(u, { headers: { "x-keepsnappy-key": t.key, accept: "application/json" } });
13918
+ const headers = { "x-keepsnappy-key": t.key, accept: "application/json" };
13919
+ if (send.body !== undefined)
13920
+ headers["content-type"] = "application/json";
13921
+ const r = await (t.fetch ?? fetch)(u, { method: send.method ?? "GET", headers, ...send.body !== undefined ? { body: JSON.stringify(send.body) } : {} });
13817
13922
  const text = await r.text();
13818
13923
  let body;
13819
13924
  try {
@@ -13825,7 +13930,7 @@ async function get(t, path, query = {}) {
13825
13930
  if (!r.ok) {
13826
13931
  const code = typeof body.error === "string" ? body.error : String(r.status);
13827
13932
  const message = typeof body.message === "string" ? body.message : code;
13828
- const hint = r.status === 401 ? " (check KEEPSNAPPY_KEY)" : r.status === 403 ? " (the key needs the read or run scope)" : "";
13933
+ const hint = r.status === 401 ? " (check KEEPSNAPPY_KEY)" : r.status === 403 ? ` (the key needs the ${send.scope ?? "read or run"} scope)` : "";
13829
13934
  throw new ApiError(r.status, code, `${message}${hint}`);
13830
13935
  }
13831
13936
  return body;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@keepsnappy/agent",
3
- "version": "0.1.0-alpha.20",
3
+ "version": "0.1.0-alpha.21",
4
4
  "description": "KeepSnappy CLI: runs your app journeys, measures from inside the app and reports More work / Less work / No change per step.",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",