harnery 0.37.0 → 0.38.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (184) hide show
  1. package/dist/commander.d.ts +9 -0
  2. package/dist/commander.d.ts.map +1 -1
  3. package/dist/commander.js +7 -2
  4. package/dist/commands/admission.d.ts +21 -0
  5. package/dist/commands/admission.d.ts.map +1 -0
  6. package/dist/commands/admission.js +565 -0
  7. package/dist/commands/agents.d.ts +7 -0
  8. package/dist/commands/agents.d.ts.map +1 -1
  9. package/dist/commands/agents.js +61 -1
  10. package/dist/commands/artifacts.d.ts.map +1 -1
  11. package/dist/commands/artifacts.js +48 -2
  12. package/dist/commands/browse-ai.d.ts +2 -2
  13. package/dist/commands/browse-ai.d.ts.map +1 -1
  14. package/dist/commands/browse-ai.js +6 -4
  15. package/dist/commands/browse.d.ts.map +1 -1
  16. package/dist/commands/browse.js +349 -21
  17. package/dist/commands/fetch.js +1 -0
  18. package/dist/commands/qa-record.d.ts +144 -0
  19. package/dist/commands/qa-record.d.ts.map +1 -0
  20. package/dist/commands/qa-record.js +0 -0
  21. package/dist/commands/qa-run.d.ts +7 -3
  22. package/dist/commands/qa-run.d.ts.map +1 -1
  23. package/dist/commands/qa-run.js +268 -13
  24. package/dist/commands/qa-status.d.ts +71 -0
  25. package/dist/commands/qa-status.d.ts.map +1 -0
  26. package/dist/commands/qa-status.js +490 -0
  27. package/dist/commands/qa-verify.d.ts +40 -0
  28. package/dist/commands/qa-verify.d.ts.map +1 -0
  29. package/dist/commands/qa-verify.js +180 -0
  30. package/dist/commands/review-pack.d.ts +4 -0
  31. package/dist/commands/review-pack.d.ts.map +1 -0
  32. package/dist/commands/review-pack.js +1001 -0
  33. package/dist/core/agents/qa-signal.d.ts +111 -0
  34. package/dist/core/agents/qa-signal.d.ts.map +1 -0
  35. package/dist/core/agents/qa-signal.js +231 -0
  36. package/dist/core/agents/session-name-display.d.ts +20 -5
  37. package/dist/core/agents/session-name-display.d.ts.map +1 -1
  38. package/dist/core/agents/session-name-display.js +67 -7
  39. package/dist/core/agents/state/heartbeat-reader.d.ts +7 -0
  40. package/dist/core/agents/state/heartbeat-reader.d.ts.map +1 -1
  41. package/dist/core/agents/state/heartbeat-writer.d.ts +11 -0
  42. package/dist/core/agents/state/heartbeat-writer.d.ts.map +1 -1
  43. package/dist/core/agents/state/heartbeat-writer.js +17 -0
  44. package/dist/core/agents/state/live-coordination-view.d.ts.map +1 -1
  45. package/dist/core/agents/state/live-coordination-view.js +1 -0
  46. package/dist/core/agents/state/live-coordination-writer.js +5 -0
  47. package/dist/core/artifacts/constants.d.ts +1 -1
  48. package/dist/core/artifacts/constants.js +1 -1
  49. package/dist/core/artifacts/index.d.ts +57 -6
  50. package/dist/core/artifacts/index.d.ts.map +1 -1
  51. package/dist/core/artifacts/index.js +265 -10
  52. package/dist/core/config.d.ts +8 -0
  53. package/dist/core/config.d.ts.map +1 -1
  54. package/dist/core/config.js +16 -0
  55. package/dist/core/diagnostics/bundle.d.ts +16 -0
  56. package/dist/core/diagnostics/bundle.d.ts.map +1 -1
  57. package/dist/core/diagnostics/bundle.js +101 -7
  58. package/dist/core/events/v3/bootstrap.d.ts.map +1 -1
  59. package/dist/core/events/v3/bootstrap.js +10 -0
  60. package/dist/core/events/v3/coordination-view.d.ts +3 -0
  61. package/dist/core/events/v3/coordination-view.d.ts.map +1 -1
  62. package/dist/core/events/v3/coordination-view.js +69 -8
  63. package/dist/core/events/v3/producers/intake.d.ts.map +1 -1
  64. package/dist/core/events/v3/producers/intake.js +9 -2
  65. package/dist/core/events/v3/producers/recorder.d.ts +23 -0
  66. package/dist/core/events/v3/producers/recorder.d.ts.map +1 -1
  67. package/dist/core/events/v3/producers/recorder.js +264 -18
  68. package/dist/core/hooks/cli.js +120 -27
  69. package/dist/core/hooks/resolve/transcript.d.ts.map +1 -1
  70. package/dist/core/hooks/resolve/transcript.js +10 -3
  71. package/dist/core/hooks/session-name-presence.d.ts.map +1 -1
  72. package/dist/core/hooks/session-name-presence.js +4 -1
  73. package/dist/core/qa-artifacts.d.ts +20 -0
  74. package/dist/core/qa-artifacts.d.ts.map +1 -0
  75. package/dist/core/qa-artifacts.js +110 -0
  76. package/dist/core/resources/contract.d.ts +6 -0
  77. package/dist/core/resources/contract.d.ts.map +1 -1
  78. package/dist/core/resources/sampler.d.ts +7 -0
  79. package/dist/core/resources/sampler.d.ts.map +1 -1
  80. package/dist/core/resources/sampler.js +106 -5
  81. package/dist/lib/admission.d.ts +71 -0
  82. package/dist/lib/admission.d.ts.map +1 -0
  83. package/dist/lib/admission.js +264 -0
  84. package/dist/lib/agent-browser/client.d.ts +1 -1
  85. package/dist/lib/agent-browser/client.d.ts.map +1 -1
  86. package/dist/lib/agent-browser/client.js +1 -5
  87. package/dist/lib/browser/capture-fidelity.d.ts +39 -0
  88. package/dist/lib/browser/capture-fidelity.d.ts.map +1 -0
  89. package/dist/lib/browser/capture-fidelity.js +84 -0
  90. package/dist/lib/browser/client.d.ts +41 -1
  91. package/dist/lib/browser/client.d.ts.map +1 -1
  92. package/dist/lib/browser/client.js +167 -9
  93. package/dist/lib/browser/critique.d.ts +38 -1
  94. package/dist/lib/browser/critique.d.ts.map +1 -1
  95. package/dist/lib/browser/critique.js +34 -6
  96. package/dist/lib/browser/index.d.ts +4 -2
  97. package/dist/lib/browser/index.d.ts.map +1 -1
  98. package/dist/lib/browser/index.js +2 -0
  99. package/dist/lib/browser/page-review-judge.d.ts +64 -0
  100. package/dist/lib/browser/page-review-judge.d.ts.map +1 -0
  101. package/dist/lib/browser/page-review-judge.js +270 -0
  102. package/dist/lib/browser/page-review-pack.d.ts +613 -0
  103. package/dist/lib/browser/page-review-pack.d.ts.map +1 -0
  104. package/dist/lib/browser/page-review-pack.js +1751 -0
  105. package/dist/lib/browser/qa-run-contracts.d.ts +214 -10
  106. package/dist/lib/browser/qa-run-contracts.d.ts.map +1 -1
  107. package/dist/lib/browser/qa-run-contracts.js +136 -1
  108. package/dist/lib/browser/qa-run.d.ts +100 -10
  109. package/dist/lib/browser/qa-run.d.ts.map +1 -1
  110. package/dist/lib/browser/qa-run.js +768 -169
  111. package/dist/lib/browser/request-diagnostics.d.ts +13 -0
  112. package/dist/lib/browser/request-diagnostics.d.ts.map +1 -0
  113. package/dist/lib/browser/request-diagnostics.js +18 -0
  114. package/dist/lib/browser/tiling.d.ts +19 -0
  115. package/dist/lib/browser/tiling.d.ts.map +1 -1
  116. package/dist/lib/browser/tiling.js +28 -0
  117. package/dist/lib/cookies/client.d.ts +9 -0
  118. package/dist/lib/cookies/client.d.ts.map +1 -1
  119. package/dist/lib/cookies/client.js +197 -44
  120. package/dist/lib/cookies/extra.d.ts +18 -0
  121. package/dist/lib/cookies/extra.d.ts.map +1 -0
  122. package/dist/lib/cookies/extra.js +14 -0
  123. package/dist/lib/cookies/index.d.ts +2 -1
  124. package/dist/lib/cookies/index.d.ts.map +1 -1
  125. package/dist/lib/cookies/index.js +2 -1
  126. package/dist/lib/durable-job.d.ts +124 -0
  127. package/dist/lib/durable-job.d.ts.map +1 -0
  128. package/dist/lib/durable-job.js +296 -0
  129. package/dist/lib/http/client.d.ts +7 -1
  130. package/dist/lib/http/client.d.ts.map +1 -1
  131. package/dist/lib/http/client.js +2 -0
  132. package/dist/lib/instructions/templates.d.ts.map +1 -1
  133. package/dist/lib/instructions/templates.js +5 -2
  134. package/package.json +8 -2
  135. package/src/commander.ts +50 -2
  136. package/src/commands/admission.ts +699 -0
  137. package/src/commands/agents.ts +87 -1
  138. package/src/commands/artifacts.ts +97 -21
  139. package/src/commands/browse-ai.ts +10 -5
  140. package/src/commands/browse.ts +481 -20
  141. package/src/commands/fetch.ts +1 -0
  142. package/src/commands/qa-record.ts +682 -0
  143. package/src/commands/qa-run.ts +335 -16
  144. package/src/commands/qa-status.ts +608 -0
  145. package/src/commands/qa-verify.ts +238 -0
  146. package/src/commands/review-pack.ts +1281 -0
  147. package/src/core/agents/qa-signal.ts +261 -0
  148. package/src/core/agents/session-name-display.ts +78 -7
  149. package/src/core/agents/state/heartbeat-reader.ts +7 -0
  150. package/src/core/agents/state/heartbeat-writer.ts +23 -0
  151. package/src/core/agents/state/live-coordination-view.ts +1 -0
  152. package/src/core/agents/state/live-coordination-writer.ts +5 -0
  153. package/src/core/artifacts/constants.ts +1 -1
  154. package/src/core/artifacts/index.ts +370 -21
  155. package/src/core/config.ts +23 -0
  156. package/src/core/diagnostics/bundle.ts +119 -11
  157. package/src/core/events/v3/bootstrap.ts +10 -0
  158. package/src/core/events/v3/coordination-view.ts +100 -11
  159. package/src/core/events/v3/producers/intake.ts +9 -2
  160. package/src/core/events/v3/producers/recorder.ts +312 -18
  161. package/src/core/hooks/cli.ts +140 -32
  162. package/src/core/hooks/resolve/transcript.ts +10 -3
  163. package/src/core/hooks/session-name-presence.ts +6 -1
  164. package/src/core/qa-artifacts.ts +126 -0
  165. package/src/core/resources/contract.ts +7 -0
  166. package/src/core/resources/sampler.ts +138 -6
  167. package/src/lib/admission.ts +347 -0
  168. package/src/lib/agent-browser/client.ts +2 -10
  169. package/src/lib/browser/capture-fidelity.ts +98 -0
  170. package/src/lib/browser/client.ts +206 -10
  171. package/src/lib/browser/critique.ts +62 -7
  172. package/src/lib/browser/index.ts +36 -0
  173. package/src/lib/browser/page-review-judge.ts +360 -0
  174. package/src/lib/browser/page-review-pack.ts +2384 -0
  175. package/src/lib/browser/qa-run-contracts.ts +366 -3
  176. package/src/lib/browser/qa-run.ts +868 -190
  177. package/src/lib/browser/request-diagnostics.ts +27 -0
  178. package/src/lib/browser/tiling.ts +32 -0
  179. package/src/lib/cookies/client.ts +228 -42
  180. package/src/lib/cookies/extra.ts +28 -0
  181. package/src/lib/cookies/index.ts +2 -0
  182. package/src/lib/durable-job.ts +407 -0
  183. package/src/lib/http/client.ts +13 -1
  184. package/src/lib/instructions/templates.ts +5 -2
@@ -3,10 +3,13 @@ import { homedir } from "node:os";
3
3
  import { dirname, resolve } from "node:path";
4
4
  import axe from "axe-core";
5
5
  import { chromium, } from "playwright";
6
+ import { CookieStoreParseError, } from "../cookies/index.js";
6
7
  import { buildAssertCheck } from "./asserts.js";
8
+ import { stitchPngRows } from "./capture-fidelity.js";
7
9
  import { buildClearContentAnnotationsScript, buildContentAnnotateScript, buildContentChecks, } from "./content-checks.js";
8
10
  import { buildClearLayoutLintAnnotationsScript, buildLayoutLintAnnotateScript, buildLayoutLintCheck, } from "./geometry.js";
9
11
  import { buildClearLayoutAnnotationsScript, buildLayoutAnnotateScript, buildOverflowCheck, buildWidthCheck, } from "./layout.js";
12
+ import { isBenignNextRscAbort } from "./request-diagnostics.js";
10
13
  import { buildClearRuntsAnnotationsScript, buildRuntsAnnotateScript, buildRuntsCheck, } from "./runts.js";
11
14
  import { buildStandaloneHtmlScript, DEFAULT_MAX_RESOURCE_BYTES, DEFAULT_MAX_TOTAL_RESOURCE_BYTES, } from "./standalone-html.js";
12
15
  import { buildClearTargetSizeAnnotationsScript, buildTargetSizeAnnotateScript, buildTargetSizeCheck, } from "./target-size.js";
@@ -81,12 +84,16 @@ export class Browser {
81
84
  * the whole sequence — not just the launch — can be retried as a unit.
82
85
  */
83
86
  async openOnce() {
87
+ const jarCookies = this.opts.jar?.list() ?? [];
84
88
  // Hold a launch slot only across the spawn itself, not the browser's life.
85
89
  await acquireLaunchSlot();
86
90
  try {
87
91
  this.context = await chromium.launchPersistentContext(this.profileDir, {
88
92
  headless: !this.opts.headed,
89
93
  viewport: this.opts.viewport ?? { width: 1280, height: 800 },
94
+ ...(this.opts.deviceScaleFactor !== undefined
95
+ ? { deviceScaleFactor: this.opts.deviceScaleFactor }
96
+ : {}),
90
97
  ...(this.opts.colorScheme ? { colorScheme: this.opts.colorScheme } : {}),
91
98
  ...(this.opts.launchTimeout !== undefined ? { timeout: this.opts.launchTimeout } : {}),
92
99
  ...(this.opts.launchArgs && this.opts.launchArgs.length > 0
@@ -102,11 +109,8 @@ export class Browser {
102
109
  releaseLaunchSlot();
103
110
  }
104
111
  this.context.setDefaultNavigationTimeout(this.opts.navigationTimeout ?? 30_000);
105
- if (this.opts.jar) {
106
- const jarCookies = this.opts.jar.list();
107
- if (jarCookies.length > 0) {
108
- await this.context.addCookies(jarCookies.map(toPWCookie));
109
- }
112
+ if (jarCookies.length > 0) {
113
+ await this.context.addCookies(jarCookies.map(toPWCookie));
110
114
  }
111
115
  // Caller-injected extraHeaders callback (e.g., for Cloudflare-bypass
112
116
  // or custom auth headers). Per-request route handler so headers only
@@ -171,6 +175,11 @@ export class Browser {
171
175
  this.consoleEvents = [];
172
176
  this.pageErrors = [];
173
177
  this.failedRequests = [];
178
+ if (err instanceof CookieStoreParseError) {
179
+ throw new Error(`Failed to open browser during cookie-store load: ${err.message}`, {
180
+ cause: err,
181
+ });
182
+ }
174
183
  if (attempt < maxAttempts) {
175
184
  await new Promise((resolve) => setTimeout(resolve, attempt * 250));
176
185
  }
@@ -197,11 +206,16 @@ export class Browser {
197
206
  this.pageErrors.push({ message: err.message, stack: err.stack });
198
207
  });
199
208
  page.on("requestfailed", (req) => {
200
- this.failedRequests.push({
209
+ const failedRequest = {
201
210
  url: req.url(),
202
211
  method: req.method(),
203
212
  failure: req.failure()?.errorText ?? "unknown",
204
213
  resourceType: req.resourceType(),
214
+ };
215
+ if (isBenignNextRscAbort(failedRequest))
216
+ return;
217
+ this.failedRequests.push({
218
+ ...failedRequest,
205
219
  status: null,
206
220
  kind: "network",
207
221
  });
@@ -357,6 +371,152 @@ export class Browser {
357
371
  const buf = await this.currentPage.screenshot({ type: "png", clip: rect });
358
372
  return buf.toString("base64");
359
373
  }
374
+ /**
375
+ * PNG of a document-space rect captured the way a reader sees it: scroll so
376
+ * the rect's top sits at the top of the viewport, wait for the scroll to
377
+ * settle (instant scroll, then rAF frames until `scrollY` stops moving),
378
+ * and clip the viewport. Restores the scroll position afterwards.
379
+ *
380
+ * Bands are taller than the viewport. Rather than resizing the viewport to
381
+ * the band height (which reflows `vh`-sized layout, moves sticky and fixed
382
+ * elements, and so stops matching what the user sees), the rect is captured
383
+ * in viewport-height pieces at the real viewport and the pieces are
384
+ * stitched with pngjs. The final piece of a multi-piece rect is anchored to
385
+ * the viewport bottom so a fixed header, which sits at the viewport top, is
386
+ * not repeated in the middle of the band. No viewport change is made.
387
+ *
388
+ * Pixels come back at the page's device scale factor, so a rect at DPR 2
389
+ * yields an image twice the rect's CSS size, the same as a full-page
390
+ * screenshot cropped in pixel space would.
391
+ *
392
+ * Parity with the full-page screenshot: a full-page capture renders the
393
+ * whole document as one viewport, so a fixed header or a stuck sticky bar
394
+ * appears once, at its document position. Scrolled, the same element would
395
+ * ride along at the top of every piece and differ from the full-page crop
396
+ * on every band but the first. So while a piece is captured at a non-zero
397
+ * scroll, every `position: fixed`/`sticky` element that has left its
398
+ * scroll-0 document position is given `visibility: hidden` (layout kept,
399
+ * inline style restored afterwards). The result is the region as a
400
+ * full-page screenshot would show it, without the full-page raster.
401
+ */
402
+ async captureRegionByScroll(rect) {
403
+ const page = this.currentPage;
404
+ if (!(rect.width > 0) || !(rect.height > 0)) {
405
+ throw new Error(`captureRegionByScroll: rect must have a positive size (got ${rect.width}×${rect.height})`);
406
+ }
407
+ const settle = (top) => page.evaluate(async (target) => {
408
+ window.scrollTo({ left: window.scrollX, top: target, behavior: "instant" });
409
+ const frame = () => new Promise((r) => requestAnimationFrame(() => r()));
410
+ let last = Number.NaN;
411
+ for (let i = 0; i < 8; i++) {
412
+ await frame();
413
+ const now = window.scrollY;
414
+ if (now === last)
415
+ break;
416
+ last = now;
417
+ }
418
+ return {
419
+ scrollX: window.scrollX,
420
+ scrollY: window.scrollY,
421
+ innerWidth: window.innerWidth,
422
+ innerHeight: window.innerHeight,
423
+ };
424
+ }, top);
425
+ const original = await page.evaluate(() => ({ x: window.scrollX, y: window.scrollY }));
426
+ const pieces = [];
427
+ // Viewport-pinned candidates, measured at scroll 0 so each later capture
428
+ // can tell "still where the full page shows it" from "riding along".
429
+ await settle(0);
430
+ // Hiding uses opacity, not visibility: `visibility: hidden` on the pinned
431
+ // element is undone by any descendant that sets `visibility: visible`
432
+ // (a revealed brand label inside a sticky bar did exactly that), while
433
+ // opacity is a group effect no descendant can override. Transitions are
434
+ // switched off inline so the capture never lands mid-fade.
435
+ const pinned = await page.evaluateHandle(() => {
436
+ const out = [];
437
+ const all = document.querySelectorAll("body *");
438
+ const limit = Math.min(all.length, 20_000);
439
+ for (let i = 0; i < limit; i++) {
440
+ const el = all[i];
441
+ const position = getComputedStyle(el).position;
442
+ if (position !== "fixed" && position !== "sticky")
443
+ continue;
444
+ out.push({
445
+ el,
446
+ top: el.getBoundingClientRect().top + window.scrollY,
447
+ priorOpacity: el.style.getPropertyValue("opacity"),
448
+ priorOpacityPriority: el.style.getPropertyPriority("opacity"),
449
+ priorTransition: el.style.getPropertyValue("transition"),
450
+ priorTransitionPriority: el.style.getPropertyPriority("transition"),
451
+ });
452
+ }
453
+ return out;
454
+ });
455
+ const syncPinned = (mode) => page.evaluate(async ([entries, action]) => {
456
+ for (const entry of entries) {
457
+ const restore = () => {
458
+ entry.el.style.setProperty("opacity", entry.priorOpacity, entry.priorOpacityPriority);
459
+ entry.el.style.setProperty("transition", entry.priorTransition, entry.priorTransitionPriority);
460
+ };
461
+ if (action === "restore") {
462
+ restore();
463
+ continue;
464
+ }
465
+ const now = entry.el.getBoundingClientRect().top + window.scrollY;
466
+ if (Math.abs(now - entry.top) > 1) {
467
+ entry.el.style.setProperty("transition", "none", "important");
468
+ entry.el.style.setProperty("opacity", "0", "important");
469
+ }
470
+ else {
471
+ restore();
472
+ }
473
+ }
474
+ // Let the style change paint before the screenshot is taken.
475
+ await new Promise((r) => requestAnimationFrame(() => r()));
476
+ }, [pinned, mode]);
477
+ try {
478
+ const bottom = rect.y + rect.height;
479
+ let cursor = rect.y;
480
+ let first = true;
481
+ while (cursor < bottom) {
482
+ const remaining = bottom - cursor;
483
+ // Top-align the piece; a final partial piece is then re-anchored to
484
+ // the viewport bottom once the viewport height is known.
485
+ let view = await settle(cursor);
486
+ if (!first && remaining < view.innerHeight) {
487
+ view = await settle(Math.max(0, bottom - view.innerHeight));
488
+ }
489
+ await syncPinned("hide-moved");
490
+ const offset = cursor - view.scrollY;
491
+ if (offset < 0 || offset >= view.innerHeight) {
492
+ throw new Error(`captureRegionByScroll: document y ${cursor} is not reachable in the viewport ` +
493
+ `(scrollY ${view.scrollY}, innerHeight ${view.innerHeight})`);
494
+ }
495
+ const pieceHeight = Math.min(remaining, view.innerHeight - offset);
496
+ const clipX = Math.max(0, rect.x - view.scrollX);
497
+ const clipWidth = Math.min(rect.width, view.innerWidth - clipX);
498
+ if (!(clipWidth > 0)) {
499
+ throw new Error(`captureRegionByScroll: document x ${rect.x} is outside the viewport (innerWidth ${view.innerWidth})`);
500
+ }
501
+ pieces.push(await page.screenshot({
502
+ type: "png",
503
+ clip: { x: clipX, y: offset, width: clipWidth, height: pieceHeight },
504
+ }));
505
+ cursor += pieceHeight;
506
+ first = false;
507
+ }
508
+ }
509
+ finally {
510
+ try {
511
+ await syncPinned("restore");
512
+ }
513
+ finally {
514
+ await pinned.dispose();
515
+ await page.evaluate(({ x, y }) => window.scrollTo({ left: x, top: y, behavior: "instant" }), original);
516
+ }
517
+ }
518
+ return stitchPngRows(pieces);
519
+ }
360
520
  /** Document-space rects + labels for each element matching `selector` (semantic tiling). */
361
521
  async elementTiles(selector) {
362
522
  return await this.currentPage.evaluate((sel) => {
@@ -1230,9 +1390,7 @@ export class Browser {
1230
1390
  if (this.opts.jar) {
1231
1391
  try {
1232
1392
  const live = await this.context.cookies();
1233
- for (const c of live) {
1234
- this.opts.jar.set(toJarCookie(c));
1235
- }
1393
+ this.opts.jar.merge(live.map(toJarCookie));
1236
1394
  }
1237
1395
  catch {
1238
1396
  // Cookie persist is best-effort; never block close on it.
@@ -19,10 +19,26 @@ export interface CritiqueFinding {
19
19
  category: string;
20
20
  description: string;
21
21
  }
22
+ /**
23
+ * How much of the page the tiles actually cover. The tiler keeps at most
24
+ * `maxTiles` bands and drops the rest, so on a tall page `capped` is true and
25
+ * `reviewed_height_px` stops short of `page_height_px`. Callers that turn a
26
+ * critique into a verdict must read this: a pass over the first 24 bands is
27
+ * not a pass over the page.
28
+ */
29
+ export interface CritiqueCoverage {
30
+ page_height_px: number;
31
+ reviewed_height_px: number;
32
+ bands_total: number;
33
+ bands_reviewed: number;
34
+ capped: boolean;
35
+ }
22
36
  export interface CritiqueResult {
23
37
  rule: "critique";
24
38
  /** Number of tiles the page was cut into. */
25
39
  tiles: number;
40
+ /** Tile coverage of the page; absent only for results built without tiles. */
41
+ coverage?: CritiqueCoverage;
26
42
  /** Whether a provider was available. False → outcome "skipped". */
27
43
  provider: boolean;
28
44
  findings: CritiqueFinding[];
@@ -82,6 +98,15 @@ export declare function bandRects(pageHeight: number, pageWidth: number, bandHei
82
98
  * the provider would downscale. Atom coordinates must be in the same pixel
83
99
  * space as the screenshot (identical at deviceScaleFactor 1; scale them if
84
100
  * the capture DPR differs).
101
+ *
102
+ * `hitRects` (document-space rectangles a deterministic gate already flagged)
103
+ * add bands past the `maxTiles` cap: every full-page band that intersects a
104
+ * hit rect and was dropped by the cap is cut as well, labelled `hit band N`
105
+ * (N is the band's 1-based position in the full band list, and the tile's
106
+ * `index` is that position minus one), and appended after the kept bands.
107
+ * `coverage` stays honest about the kept prefix; `hitBands` counts the
108
+ * extras. Ignored for selector tiling (`elementRects`), which has no cap to
109
+ * look past in that sense.
85
110
  */
86
111
  export declare function tilesFromFullPage(fullPageBuffer: Buffer, opts: {
87
112
  elementRects?: Array<{
@@ -95,7 +120,19 @@ export declare function tilesFromFullPage(fullPageBuffer: Buffer, opts: {
95
120
  overlap?: number;
96
121
  maxTiles: number;
97
122
  atoms?: VisualAtom[];
98
- }): CritiqueTile[];
123
+ hitRects?: Array<{
124
+ x: number;
125
+ y: number;
126
+ width: number;
127
+ height: number;
128
+ }>;
129
+ /** Cap on hit bands appended past `maxTiles` (default 50). */
130
+ maxHitBands?: number;
131
+ }): {
132
+ tiles: CritiqueTile[];
133
+ coverage: CritiqueCoverage;
134
+ hitBands: number;
135
+ };
99
136
  /**
100
137
  * Validate + normalize a provider's raw output into CritiqueFindings. Tolerant
101
138
  * of a model returning a bare array or wrapping it in `{ findings: [...] }`,
@@ -1 +1 @@
1
- {"version":3,"file":"critique.d.ts","sourceRoot":"","sources":["../../../src/lib/browser/critique.ts"],"names":[],"mappings":"AAoBA,OAAO,EAAuC,KAAK,UAAU,EAAE,MAAM,aAAa,CAAC;AAEnF,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,yDAAyD;IACzD,KAAK,EAAE,MAAM,CAAC;IACd,6EAA6E;IAC7E,OAAO,EAAE,MAAM,CAAC;IAChB;mFAC+E;IAC/E,CAAC,CAAC,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,gDAAgD;IAChD,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,GAAG,QAAQ,GAAG,KAAK,CAAC;IACpC,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,UAAU,CAAC;IACjB,6CAA6C;IAC7C,KAAK,EAAE,MAAM,CAAC;IACd,mEAAmE;IACnE,QAAQ,EAAE,OAAO,CAAC;IAClB,QAAQ,EAAE,eAAe,EAAE,CAAC;IAC5B,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;IACrC,kDAAkD;IAClD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,6EAA6E;IAC7E,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACzC;AAED;;;;;;;;;;GAUG;AACH,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,EAAE;IACtC,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,YAAY,CAAC;CACpB,KAAK,OAAO,CAAC,eAAe,EAAE,CAAC,CAAC,GAAG;IAClC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC;IACjD;iFAC6E;IAC7E,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,CAAC;AAEF,eAAO,MAAM,uBAAuB,QAiBxB,CAAC;AAEb;;;;GAIG;AACH,wBAAgB,SAAS,CACvB,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,MAAM,GACd,KAAK,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAY/E;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,iBAAiB,CAC/B,cAAc,EAAE,MAAM,EACtB,IAAI,EAAE;IACJ,YAAY,CAAC,EAAE,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC7F,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,UAAU,EAAE,CAAC;CACtB,GACA,YAAY,EAAE,CA+DhB;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,eAAe,EAAE,CAyB/E;AAED;;;;;GAKG;AACH,wBAAsB,WAAW,CAAC,IAAI,EAAE;IACtC,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,YAAY,EAAE,CAAC;IACtB,QAAQ,EAAE,gBAAgB,GAAG,SAAS,CAAC;CACxC,GAAG,OAAO,CAAC,cAAc,CAAC,CAkD1B"}
1
+ {"version":3,"file":"critique.d.ts","sourceRoot":"","sources":["../../../src/lib/browser/critique.ts"],"names":[],"mappings":"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"}
@@ -17,7 +17,7 @@
17
17
  // false pass. That keeps this portable — the tiling, prompt, and
18
18
  // orchestration live here; the model call lives in the host.
19
19
  import { PNG } from "pngjs";
20
- import { bandOversizedRect, snappedBandRects } from "./tiling.js";
20
+ import { bandOversizedRect, bandsCoveringRects, snappedBandRects, } from "./tiling.js";
21
21
  export const DEFAULT_CRITIQUE_RUBRIC = [
22
22
  "You are reviewing one vertical slice of a rendered web page for visual defects.",
23
23
  "Report ONLY concrete, visible problems a careful designer would flag:",
@@ -71,6 +71,15 @@ export function bandRects(pageHeight, pageWidth, bandHeight, overlap) {
71
71
  * the provider would downscale. Atom coordinates must be in the same pixel
72
72
  * space as the screenshot (identical at deviceScaleFactor 1; scale them if
73
73
  * the capture DPR differs).
74
+ *
75
+ * `hitRects` (document-space rectangles a deterministic gate already flagged)
76
+ * add bands past the `maxTiles` cap: every full-page band that intersects a
77
+ * hit rect and was dropped by the cap is cut as well, labelled `hit band N`
78
+ * (N is the band's 1-based position in the full band list, and the tile's
79
+ * `index` is that position minus one), and appended after the kept bands.
80
+ * `coverage` stays honest about the kept prefix; `hitBands` counts the
81
+ * extras. Ignored for selector tiling (`elementRects`), which has no cap to
82
+ * look past in that sense.
74
83
  */
75
84
  export function tilesFromFullPage(fullPageBuffer, opts) {
76
85
  const png = PNG.sync.read(fullPageBuffer);
@@ -105,8 +114,7 @@ export function tilesFromFullPage(fullPageBuffer, opts) {
105
114
  height: b.height,
106
115
  }));
107
116
  }
108
- const tiles = [];
109
- for (const rect of rects.slice(0, opts.maxTiles)) {
117
+ const cutTile = (rect) => {
110
118
  const sx = Math.max(0, Math.min(Math.round(rect.x), png.width - 1));
111
119
  const sy = Math.max(0, Math.min(Math.round(rect.y), png.height - 1));
112
120
  const w = Math.max(1, Math.min(Math.round(rect.width), png.width - sx));
@@ -119,7 +127,7 @@ export function tilesFromFullPage(fullPageBuffer, opts) {
119
127
  const dstStart = row * w * 4;
120
128
  png.data.copy(dst.data, dstStart, srcStart, srcStart + w * 4);
121
129
  }
122
- tiles.push({
130
+ return {
123
131
  index: rect.index,
124
132
  label: rect.label,
125
133
  scrollY: sy,
@@ -127,9 +135,29 @@ export function tilesFromFullPage(fullPageBuffer, opts) {
127
135
  width: w,
128
136
  height: h,
129
137
  pngBase64: PNG.sync.write(dst).toString("base64"),
130
- });
138
+ };
139
+ };
140
+ const kept = rects.slice(0, opts.maxTiles);
141
+ const tiles = kept.map(cutTile);
142
+ let hitBands = 0;
143
+ const banding = !(opts.elementRects && opts.elementRects.length > 0);
144
+ if (banding && opts.hitRects && opts.hitRects.length > 0) {
145
+ for (const index of bandsCoveringRects(rects, kept.length, opts.hitRects, opts.maxHitBands)) {
146
+ const band = rects[index];
147
+ if (!band)
148
+ continue;
149
+ tiles.push(cutTile({ ...band, label: `hit band ${index + 1}` }));
150
+ hitBands++;
151
+ }
131
152
  }
132
- return tiles;
153
+ const coverage = {
154
+ page_height_px: png.height,
155
+ reviewed_height_px: kept.reduce((max, rect) => Math.max(max, Math.min(png.height, Math.round(rect.y + rect.height))), 0),
156
+ bands_total: rects.length,
157
+ bands_reviewed: kept.length,
158
+ capped: rects.length > kept.length,
159
+ };
160
+ return { tiles, coverage, hitBands };
133
161
  }
134
162
  /**
135
163
  * Validate + normalize a provider's raw output into CritiqueFindings. Tolerant
@@ -1,17 +1,19 @@
1
1
  export { type AssertOp, type AssertResult, type AssertSpec, buildAssertCheck, parseAssertSpec, } from "./asserts.js";
2
2
  export { Browser, type BrowserOptions, BrowserSessionActionError, type BrowserSessionControl, type BrowserSessionInspection, type BrowserSessionLocator, type BrowserSessionNavigation, type BrowserSessionNavigationType, type BrowserSessionScreenshot, type BrowserSessionStatus, type BrowserSessionTab, type ConsoleEvent, type Diagnostics, type FailedRequest, type NavigateResult, type PageErrorEvent, } from "./client.js";
3
3
  export type { CheckRect, ContentAnnotationBox, ContentChecksRequest, ContentChecksResult, ContrastHit, ContrastResult, ImageHealthResult, ImageHit, PlaceholderHit, PlaceholderResult, TruncationHit, TruncationResult, } from "./content-checks.js";
4
- export { bandRects, type CritiqueFinding, type CritiqueProvider, type CritiqueResult, type CritiqueTile, DEFAULT_CRITIQUE_RUBRIC, normalizeFindings, runCritique, tilesFromFullPage, } from "./critique.js";
4
+ export { bandRects, type CritiqueCoverage, type CritiqueFinding, type CritiqueProvider, type CritiqueResult, type CritiqueTile, DEFAULT_CRITIQUE_RUBRIC, normalizeFindings, runCritique, tilesFromFullPage, } from "./critique.js";
5
5
  export { captureDevOverlay, type DevOverlayError, type DevOverlayResult, } from "./dev-overlay.js";
6
6
  export type { AlignChild, AlignCluster, AlignResult, ClipIssue, ClipResult, CrowdPair, CrowdResult, GapCluster, GapPair, GapResult, LayoutAxis, LayoutElementMeasurement, LayoutExclusion, LayoutLintRequest, LayoutLintResult, LayoutOutcome, LayoutRect, OverlapIssue, OverlapResult, } from "./geometry.js";
7
7
  export { buildLayoutLintCheck } from "./geometry.js";
8
8
  export { isWSL, wslHeadedLaunchArgs } from "./launch-args.js";
9
9
  export type { OverflowElement, OverflowResult, WidthResult, } from "./layout.js";
10
10
  export { serializeNetscapeCookies, writeNetscapeCookieFile, } from "./netscape-cookies.js";
11
+ export { type JudgedContext, type JudgePageReviewPackOptions, type JudgePageReviewPackResult, judgePageReviewPack, toCritiqueRecords, } from "./page-review-judge.js";
12
+ export { buildInspectionPlan, type FinalizePageReviewPackInput, finalizePageReviewPack, findingsSchemaDocument, listPackContexts, PAGE_REVIEW_FINDINGS_SCHEMA, PAGE_REVIEW_PACK_DIRNAME, PAGE_REVIEW_PACK_SCHEMA, type PageReviewContextCapture, type PageReviewContextRecord, type PageReviewCritiqueRecord, type PageReviewGateRecord, type PageReviewInspectionPlan, type PageReviewPackManifest, type PageReviewTileRecord, packPaths, readPackContext, readPackDom, readPackFullPage, readPackManifest, readPackSignature, readPackTiles, renderReviewMarkdown, writePackContext, } from "./page-review-pack.js";
11
13
  export { type BrowserProxy, type BrowserProxyGate, browserProxyFromEnv, browserProxyGateFromEnv, extractObservedIp, WEBRTC_PROXY_ONLY_ARG, } from "./proxy.js";
12
14
  export { buildQaManifest, type ChangeClass, type ClassifyOptions, classifySignatures, fnv1a32, type ManifestOptions, type QaClassification, type QaContext, type QaManifest, type QaNodeSignature, type QaScope, type QaSignature, type QaStylesheetSignature, } from "./qa-plan.js";
13
15
  export { defaultQaRunExec, QA_RUN_HEADLESS_ONLY_ENV, QA_RUN_RESULT_FILENAME, type QaRunExec, type QaRunExecOptions, type QaRunExecResult, type QaRunMatrixOptions, runQaMatrix, } from "./qa-run.js";
14
- export { computeVerdict, contextIdFor, mergeCoverage, QA_RUN_JOB_SCHEMA_VERSION, QA_RUN_RESULT_SCHEMA_VERSION, type QaRunBlocker, type QaRunCheck, type QaRunCommandOutcome, type QaRunContext, type QaRunCritiqueOutcome, type QaRunInteraction, type QaRunJob, type QaRunJobValidation, type QaRunPolicy, type QaRunResult, type QaRunVerdict, validateQaRunJob, } from "./qa-run-contracts.js";
16
+ export { computeVerdict, contextIdFor, mergeCoverage, QA_RUN_JOB_SCHEMA_VERSION, QA_RUN_RESULT_SCHEMA_VERSION, type QaRunBlocker, type QaRunCheck, type QaRunCommandOutcome, type QaRunContext, type QaRunCritiqueOutcome, type QaRunCritiquePool, type QaRunInteraction, type QaRunJob, type QaRunJobValidation, type QaRunPolicy, type QaRunResult, type QaRunReviewPack, type QaRunVerdict, validateQaRunJob, } from "./qa-run-contracts.js";
15
17
  export { listQaSnapshotTargets, loadQaSnapshot, QA_SNAPSHOT_DIR, type QaSnapshotStoreOptions, qaSnapshotKey, type ResolvedQaBaseline, resolveQaBaseline, type StoredQaSnapshot, saveQaSnapshot, } from "./qa-snapshot.js";
16
18
  export type { RuntHit, RuntsResult, } from "./runts.js";
17
19
  export { BROWSER_SESSION_MAX_FILL_BYTES, BROWSER_SESSION_MAX_FRAME_BYTES, BROWSER_SESSION_MAX_INSPECTION_TEXT_BYTES, BROWSER_SESSION_PROTOCOL_VERSION, type BrowserSessionDescriptor, BrowserSessionError, type BrowserSessionRequest, type BrowserSessionResponse, type BrowserSessionResult, type BrowserSessionServer, type BrowserSessionTarget, browserSessionTransport, readBrowserSessionDescriptor, sendBrowserSessionRequest, startBrowserSessionServer, } from "./session-control.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/lib/browser/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,QAAQ,EACb,KAAK,YAAY,EACjB,KAAK,UAAU,EACf,gBAAgB,EAChB,eAAe,GAChB,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,OAAO,EACP,KAAK,cAAc,EACnB,yBAAyB,EACzB,KAAK,qBAAqB,EAC1B,KAAK,wBAAwB,EAC7B,KAAK,qBAAqB,EAC1B,KAAK,wBAAwB,EAC7B,KAAK,4BAA4B,EACjC,KAAK,wBAAwB,EAC7B,KAAK,oBAAoB,EACzB,KAAK,iBAAiB,EACtB,KAAK,YAAY,EACjB,KAAK,WAAW,EAChB,KAAK,aAAa,EAClB,KAAK,cAAc,EACnB,KAAK,cAAc,GACpB,MAAM,aAAa,CAAC;AACrB,YAAY,EACV,SAAS,EACT,oBAAoB,EACpB,oBAAoB,EACpB,mBAAmB,EACnB,WAAW,EACX,cAAc,EACd,iBAAiB,EACjB,QAAQ,EACR,cAAc,EACd,iBAAiB,EACjB,aAAa,EACb,gBAAgB,GACjB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,SAAS,EACT,KAAK,eAAe,EACpB,KAAK,gBAAgB,EACrB,KAAK,cAAc,EACnB,KAAK,YAAY,EACjB,uBAAuB,EACvB,iBAAiB,EACjB,WAAW,EACX,iBAAiB,GAClB,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,iBAAiB,EACjB,KAAK,eAAe,EACpB,KAAK,gBAAgB,GACtB,MAAM,kBAAkB,CAAC;AAC1B,YAAY,EACV,UAAU,EACV,YAAY,EACZ,WAAW,EACX,SAAS,EACT,UAAU,EACV,SAAS,EACT,WAAW,EACX,UAAU,EACV,OAAO,EACP,SAAS,EACT,UAAU,EACV,wBAAwB,EACxB,eAAe,EACf,iBAAiB,EACjB,gBAAgB,EAChB,aAAa,EACb,UAAU,EACV,YAAY,EACZ,aAAa,GACd,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AACrD,OAAO,EAAE,KAAK,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAC9D,YAAY,EACV,eAAe,EACf,cAAc,EACd,WAAW,GACZ,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,wBAAwB,EACxB,uBAAuB,GACxB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,KAAK,YAAY,EACjB,KAAK,gBAAgB,EACrB,mBAAmB,EACnB,uBAAuB,EACvB,iBAAiB,EACjB,qBAAqB,GACtB,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,eAAe,EACf,KAAK,WAAW,EAChB,KAAK,eAAe,EACpB,kBAAkB,EAClB,OAAO,EACP,KAAK,eAAe,EACpB,KAAK,gBAAgB,EACrB,KAAK,SAAS,EACd,KAAK,UAAU,EACf,KAAK,eAAe,EACpB,KAAK,OAAO,EACZ,KAAK,WAAW,EAChB,KAAK,qBAAqB,GAC3B,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,gBAAgB,EAChB,wBAAwB,EACxB,sBAAsB,EACtB,KAAK,SAAS,EACd,KAAK,gBAAgB,EACrB,KAAK,eAAe,EACpB,KAAK,kBAAkB,EACvB,WAAW,GACZ,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,cAAc,EACd,YAAY,EACZ,aAAa,EACb,yBAAyB,EACzB,4BAA4B,EAC5B,KAAK,YAAY,EACjB,KAAK,UAAU,EACf,KAAK,mBAAmB,EACxB,KAAK,YAAY,EACjB,KAAK,oBAAoB,EACzB,KAAK,gBAAgB,EACrB,KAAK,QAAQ,EACb,KAAK,kBAAkB,EACvB,KAAK,WAAW,EAChB,KAAK,WAAW,EAChB,KAAK,YAAY,EACjB,gBAAgB,GACjB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,qBAAqB,EACrB,cAAc,EACd,eAAe,EACf,KAAK,sBAAsB,EAC3B,aAAa,EACb,KAAK,kBAAkB,EACvB,iBAAiB,EACjB,KAAK,gBAAgB,EACrB,cAAc,GACf,MAAM,kBAAkB,CAAC;AAC1B,YAAY,EACV,OAAO,EACP,WAAW,GACZ,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,8BAA8B,EAC9B,+BAA+B,EAC/B,yCAAyC,EACzC,gCAAgC,EAChC,KAAK,wBAAwB,EAC7B,mBAAmB,EACnB,KAAK,qBAAqB,EAC1B,KAAK,sBAAsB,EAC3B,KAAK,oBAAoB,EACzB,KAAK,oBAAoB,EACzB,KAAK,oBAAoB,EACzB,uBAAuB,EACvB,4BAA4B,EAC5B,yBAAyB,EACzB,yBAAyB,GAC1B,MAAM,sBAAsB,CAAC;AAC9B,YAAY,EACV,qBAAqB,EACrB,oBAAoB,GACrB,MAAM,sBAAsB,CAAC;AAC9B,YAAY,EACV,cAAc,EACd,iBAAiB,EACjB,iBAAiB,EACjB,gBAAgB,GACjB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,iBAAiB,EACjB,OAAO,EACP,KAAK,WAAW,EAChB,KAAK,QAAQ,EACb,gBAAgB,EAChB,KAAK,UAAU,GAChB,MAAM,aAAa,CAAC;AACrB,YAAY,EACV,sBAAsB,EACtB,gBAAgB,EAChB,gBAAgB,GACjB,MAAM,iBAAiB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/lib/browser/index.ts"],"names":[],"mappings":"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"}
@@ -5,6 +5,8 @@ export { captureDevOverlay, } from "./dev-overlay.js";
5
5
  export { buildLayoutLintCheck } from "./geometry.js";
6
6
  export { isWSL, wslHeadedLaunchArgs } from "./launch-args.js";
7
7
  export { serializeNetscapeCookies, writeNetscapeCookieFile, } from "./netscape-cookies.js";
8
+ export { judgePageReviewPack, toCritiqueRecords, } from "./page-review-judge.js";
9
+ export { buildInspectionPlan, finalizePageReviewPack, findingsSchemaDocument, listPackContexts, PAGE_REVIEW_FINDINGS_SCHEMA, PAGE_REVIEW_PACK_DIRNAME, PAGE_REVIEW_PACK_SCHEMA, packPaths, readPackContext, readPackDom, readPackFullPage, readPackManifest, readPackSignature, readPackTiles, renderReviewMarkdown, writePackContext, } from "./page-review-pack.js";
8
10
  export { browserProxyFromEnv, browserProxyGateFromEnv, extractObservedIp, WEBRTC_PROXY_ONLY_ARG, } from "./proxy.js";
9
11
  export { buildQaManifest, classifySignatures, fnv1a32, } from "./qa-plan.js";
10
12
  export { defaultQaRunExec, QA_RUN_HEADLESS_ONLY_ENV, QA_RUN_RESULT_FILENAME, runQaMatrix, } from "./qa-run.js";
@@ -0,0 +1,64 @@
1
+ import type { CritiqueProvider } from "./critique.js";
2
+ import { type PageReviewContextRecord, type PageReviewCritiqueRecord } from "./page-review-pack.js";
3
+ import { type CritiqueReusePlan } from "./qa-reuse.js";
4
+ import { type QaSnapshotStoreOptions } from "./qa-snapshot.js";
5
+ export interface JudgePageReviewPackOptions {
6
+ packDir: string;
7
+ /** Host-injected vision call. Absent: every context reports `skipped`. */
8
+ provider: CritiqueProvider | undefined;
9
+ rubric: string;
10
+ /** Vision calls in flight across the whole pack. Default: the provider's
11
+ * own concurrency, then 4. */
12
+ concurrency?: number;
13
+ /** Subset of context ids to judge (default: every context in the pack). */
14
+ contextIds?: string[];
15
+ /** Band-diff reuse against persisted snapshots keyed by `target`. */
16
+ reuse?: {
17
+ target: string;
18
+ store?: QaSnapshotStoreOptions;
19
+ mismatchThreshold?: number;
20
+ };
21
+ /** Absolute epoch-ms deadline. Workers stop claiming tiles once past it;
22
+ * a context with unjudged tiles reports `incomplete`, never `pass`. */
23
+ deadlineAt?: number;
24
+ onLog?: (message: string) => void;
25
+ }
26
+ export interface JudgedContext extends PageReviewCritiqueRecord {
27
+ record: PageReviewContextRecord;
28
+ /** Tiles selected for review that the pool never reached (deadline). */
29
+ tiles_unjudged: number;
30
+ reuse?: Pick<CritiqueReusePlan, "tiles_total" | "tiles_reused" | "tiles_reviewed" | "invalidation" | "mismatch_threshold">;
31
+ }
32
+ export interface JudgePageReviewPackResult {
33
+ contexts: JudgedContext[];
34
+ pool: {
35
+ concurrency: number;
36
+ wall_time_ms: number;
37
+ provider: string;
38
+ };
39
+ tiles_total: number;
40
+ tiles_reviewed: number;
41
+ tiles_reused: number;
42
+ outcome: "pass" | "fail" | "skipped" | "incomplete";
43
+ provider_meta?: Record<string, unknown>;
44
+ }
45
+ /**
46
+ * Orientation the provider reads before the rubric: which band this tile is,
47
+ * where it sits on the page, how much of it repeats a neighbour, whether it
48
+ * touches the page top or bottom, and how it was captured. A tile edge that
49
+ * continues in the next band is not a clipped element, and a fixed header at
50
+ * the top of a scrolled-band capture is not a layout defect; the preamble
51
+ * says so where the pixels cannot. Adds context only: the finding taxonomy
52
+ * the rubric defines is unchanged. Empty for an unknown tile id.
53
+ */
54
+ export declare function tileRubricPreamble(record: PageReviewContextRecord, tileId: string): string;
55
+ /**
56
+ * Judge every tile in the pack through one pool. Findings keep the tiler
57
+ * index (`tile`) for compatibility with `CritiqueFinding` and add the pack's
58
+ * stable `tile_id`. A provider throw on one tile becomes a `high`
59
+ * `provider-error` finding for that tile; nothing else is affected.
60
+ */
61
+ export declare function judgePageReviewPack(options: JudgePageReviewPackOptions): Promise<JudgePageReviewPackResult>;
62
+ /** Strip the in-memory `record` so the result can be written into the pack. */
63
+ export declare function toCritiqueRecords(result: JudgePageReviewPackResult): PageReviewCritiqueRecord[];
64
+ //# sourceMappingURL=page-review-judge.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"page-review-judge.d.ts","sourceRoot":"","sources":["../../../src/lib/browser/page-review-judge.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAmB,gBAAgB,EAAgB,MAAM,eAAe,CAAC;AACrF,OAAO,EAEL,KAAK,uBAAuB,EAC5B,KAAK,wBAAwB,EAI9B,MAAM,uBAAuB,CAAC;AAE/B,OAAO,EAAE,KAAK,iBAAiB,EAAqB,MAAM,eAAe,CAAC;AAC1E,OAAO,EAAkB,KAAK,sBAAsB,EAAE,MAAM,kBAAkB,CAAC;AAE/E,MAAM,WAAW,0BAA0B;IACzC,OAAO,EAAE,MAAM,CAAC;IAChB,0EAA0E;IAC1E,QAAQ,EAAE,gBAAgB,GAAG,SAAS,CAAC;IACvC,MAAM,EAAE,MAAM,CAAC;IACf;kCAC8B;IAC9B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,2EAA2E;IAC3E,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,qEAAqE;IACrE,KAAK,CAAC,EAAE;QACN,MAAM,EAAE,MAAM,CAAC;QACf,KAAK,CAAC,EAAE,sBAAsB,CAAC;QAC/B,iBAAiB,CAAC,EAAE,MAAM,CAAC;KAC5B,CAAC;IACF;2EACuE;IACvE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACnC;AAED,MAAM,WAAW,aAAc,SAAQ,wBAAwB;IAC7D,MAAM,EAAE,uBAAuB,CAAC;IAChC,wEAAwE;IACxE,cAAc,EAAE,MAAM,CAAC;IACvB,KAAK,CAAC,EAAE,IAAI,CACV,iBAAiB,EACjB,aAAa,GAAG,cAAc,GAAG,gBAAgB,GAAG,cAAc,GAAG,oBAAoB,CAC1F,CAAC;CACH;AAED,MAAM,WAAW,yBAAyB;IACxC,QAAQ,EAAE,aAAa,EAAE,CAAC;IAC1B,IAAI,EAAE;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;IACtE,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,MAAM,CAAC;IACvB,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,GAAG,YAAY,CAAC;IACpD,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACzC;AAUD;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,uBAAuB,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CA2E1F;AAaD;;;;;GAKG;AACH,wBAAsB,mBAAmB,CACvC,OAAO,EAAE,0BAA0B,GAClC,OAAO,CAAC,yBAAyB,CAAC,CA4KpC;AAED,+EAA+E;AAC/E,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,yBAAyB,GAAG,wBAAwB,EAAE,CAI/F"}