harnery 0.38.0 → 0.40.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/agents.d.ts.map +1 -1
- package/dist/commands/agents.js +32 -8
- package/dist/commands/artifacts.d.ts.map +1 -1
- package/dist/commands/artifacts.js +97 -6
- package/dist/commands/backup.d.ts +16 -0
- package/dist/commands/backup.d.ts.map +1 -1
- package/dist/commands/backup.js +239 -33
- package/dist/commands/browse.d.ts +8 -0
- package/dist/commands/browse.d.ts.map +1 -1
- package/dist/commands/browse.js +466 -231
- package/dist/commands/diagnostics.d.ts.map +1 -1
- package/dist/commands/diagnostics.js +6 -3
- package/dist/commands/ledger-v3.d.ts.map +1 -1
- package/dist/commands/ledger-v3.js +20 -4
- package/dist/commands/qa-run.d.ts.map +1 -1
- package/dist/commands/qa-run.js +26 -3
- package/dist/commands/qa-status.d.ts.map +1 -1
- package/dist/commands/qa-status.js +7 -2
- package/dist/commands/resources.d.ts.map +1 -1
- package/dist/commands/resources.js +23 -3
- package/dist/commands/review-pack.d.ts.map +1 -1
- package/dist/commands/review-pack.js +98 -23
- package/dist/commands/tunnel.d.ts.map +1 -1
- package/dist/commands/tunnel.js +2 -2
- package/dist/core/adapters/profiles.d.ts +1 -1
- package/dist/core/agents/canonical-emit.d.ts +3 -2
- package/dist/core/agents/canonical-emit.d.ts.map +1 -1
- package/dist/core/agents/canonical-emit.js +2 -0
- package/dist/core/agents/cli.js +13 -6
- package/dist/core/agents/live-authority-v3.d.ts.map +1 -1
- package/dist/core/agents/live-authority-v3.js +5 -3
- package/dist/core/agents/live-lifecycle-v3.d.ts +2 -2
- package/dist/core/agents/live-lifecycle-v3.d.ts.map +1 -1
- package/dist/core/agents/render/prompt-context.d.ts +7 -5
- package/dist/core/agents/render/prompt-context.d.ts.map +1 -1
- package/dist/core/agents/render/prompt-context.js +63 -8
- package/dist/core/agents/session-finalizer-v3.d.ts +2 -2
- package/dist/core/agents/session-finalizer-v3.d.ts.map +1 -1
- package/dist/core/agents/state/live-coordination-view.d.ts +3 -3
- package/dist/core/agents/state/live-coordination-view.d.ts.map +1 -1
- package/dist/core/agents/state/live-coordination-view.js +2 -1
- package/dist/core/agents/state/names.d.ts +35 -2
- package/dist/core/agents/state/names.d.ts.map +1 -1
- package/dist/core/agents/state/names.js +80 -5
- package/dist/core/agents/state/stale-sweep.d.ts +2 -1
- package/dist/core/agents/state/stale-sweep.d.ts.map +1 -1
- package/dist/core/agents/state/stale-sweep.js +23 -6
- package/dist/core/agents/turn-elapsed.d.ts +51 -0
- package/dist/core/agents/turn-elapsed.d.ts.map +1 -0
- package/dist/core/agents/turn-elapsed.js +91 -0
- package/dist/core/artifacts/activity.d.ts +14 -0
- package/dist/core/artifacts/activity.d.ts.map +1 -0
- package/dist/core/artifacts/activity.js +52 -0
- package/dist/core/artifacts/delivery-card.d.ts +39 -0
- package/dist/core/artifacts/delivery-card.d.ts.map +1 -0
- package/dist/core/artifacts/delivery-card.js +282 -0
- package/dist/core/artifacts/index.d.ts +42 -7
- package/dist/core/artifacts/index.d.ts.map +1 -1
- package/dist/core/artifacts/index.js +288 -76
- package/dist/core/artifacts/mutation-lock.d.ts +2 -0
- package/dist/core/artifacts/mutation-lock.d.ts.map +1 -0
- package/dist/core/artifacts/mutation-lock.js +91 -0
- package/dist/core/backup/host-snapshot.d.ts +18 -0
- package/dist/core/backup/host-snapshot.d.ts.map +1 -0
- package/dist/core/backup/host-snapshot.js +64 -0
- package/dist/core/config.d.ts +20 -5
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +44 -10
- package/dist/core/diagnostics/advice.d.ts +38 -3
- package/dist/core/diagnostics/advice.d.ts.map +1 -1
- package/dist/core/diagnostics/advice.js +103 -99
- package/dist/core/diagnostics/bundle.d.ts.map +1 -1
- package/dist/core/diagnostics/bundle.js +111 -47
- package/dist/core/diagnostics/comparison.d.ts.map +1 -1
- package/dist/core/diagnostics/comparison.js +3 -2
- package/dist/core/diagnostics/contract.d.ts +15 -31
- package/dist/core/diagnostics/contract.d.ts.map +1 -1
- package/dist/core/diagnostics/contract.js +2 -5
- package/dist/core/diagnostics/index.d.ts +2 -0
- package/dist/core/diagnostics/index.d.ts.map +1 -1
- package/dist/core/diagnostics/index.js +2 -0
- package/dist/core/diagnostics/pressure-contract.d.ts +260 -0
- package/dist/core/diagnostics/pressure-contract.d.ts.map +1 -0
- package/dist/core/diagnostics/pressure-contract.js +70 -0
- package/dist/core/diagnostics/pressure.d.ts +25 -0
- package/dist/core/diagnostics/pressure.d.ts.map +1 -0
- package/dist/core/diagnostics/pressure.js +1138 -0
- package/dist/core/diagnostics/replay.d.ts.map +1 -1
- package/dist/core/diagnostics/replay.js +90 -10
- package/dist/core/events/v3/adapter-id.d.ts +6 -0
- package/dist/core/events/v3/adapter-id.d.ts.map +1 -0
- package/dist/core/events/v3/adapter-id.js +7 -0
- package/dist/core/events/v3/archive-retention.d.ts.map +1 -1
- package/dist/core/events/v3/archive-retention.js +3 -2
- package/dist/core/events/v3/bootstrap.d.ts +41 -4
- package/dist/core/events/v3/bootstrap.d.ts.map +1 -1
- package/dist/core/events/v3/bootstrap.js +120 -17
- package/dist/core/events/v3/capabilities.d.ts +16 -9
- package/dist/core/events/v3/capabilities.d.ts.map +1 -1
- package/dist/core/events/v3/capabilities.js +37 -2
- package/dist/core/events/v3/capability-drift.d.ts +2 -2
- package/dist/core/events/v3/capability-drift.d.ts.map +1 -1
- package/dist/core/events/v3/control-witness.d.ts +48 -5
- package/dist/core/events/v3/control-witness.d.ts.map +1 -1
- package/dist/core/events/v3/control-witness.js +91 -36
- package/dist/core/events/v3/control.d.ts.map +1 -1
- package/dist/core/events/v3/control.js +22 -2
- package/dist/core/events/v3/index.d.ts +2 -1
- package/dist/core/events/v3/index.d.ts.map +1 -1
- package/dist/core/events/v3/index.js +2 -1
- package/dist/core/events/v3/live-feed.d.ts +7 -1
- package/dist/core/events/v3/live-feed.d.ts.map +1 -1
- package/dist/core/events/v3/live-feed.js +21 -3
- package/dist/core/events/v3/live-routing.d.ts.map +1 -1
- package/dist/core/events/v3/live-routing.js +10 -2
- package/dist/core/events/v3/producers/command-recorder.d.ts +2 -2
- package/dist/core/events/v3/producers/command-recorder.d.ts.map +1 -1
- package/dist/core/events/v3/producers/command-recorder.js +2 -1
- package/dist/core/events/v3/producers/coordination-recorder.d.ts +2 -2
- package/dist/core/events/v3/producers/coordination-recorder.d.ts.map +1 -1
- package/dist/core/events/v3/producers/coordination-recorder.js +2 -1
- package/dist/core/events/v3/producers/hook-base.d.ts +2 -2
- package/dist/core/events/v3/producers/hook-base.d.ts.map +1 -1
- package/dist/core/events/v3/producers/hook-base.js +15 -5
- package/dist/core/events/v3/producers/hook.d.ts.map +1 -1
- package/dist/core/events/v3/producers/hook.js +17 -4
- package/dist/core/events/v3/producers/intake.d.ts +4 -4
- package/dist/core/events/v3/producers/intake.d.ts.map +1 -1
- package/dist/core/events/v3/producers/intake.js +3 -2
- package/dist/core/events/v3/producers/producer-state-observer.d.ts.map +1 -1
- package/dist/core/events/v3/producers/producer-state-observer.js +2 -1
- package/dist/core/events/v3/producers/recorder.d.ts +16 -5
- package/dist/core/events/v3/producers/recorder.d.ts.map +1 -1
- package/dist/core/events/v3/producers/recorder.js +31 -9
- package/dist/core/events/v3/reader.d.ts.map +1 -1
- package/dist/core/events/v3/reader.js +10 -13
- package/dist/core/events/v3/runtime-build.d.ts.map +1 -1
- package/dist/core/events/v3/runtime-build.js +3 -2
- package/dist/core/events/v3/runtime-telemetry-capabilities.d.ts +2 -2
- package/dist/core/events/v3/runtime-telemetry-capabilities.d.ts.map +1 -1
- package/dist/core/events/v3/turn-telemetry.d.ts +2 -2
- package/dist/core/events/v3/turn-telemetry.d.ts.map +1 -1
- package/dist/core/hooks/adapter/output.d.ts +16 -14
- package/dist/core/hooks/adapter/output.d.ts.map +1 -1
- package/dist/core/hooks/adapter/output.js +36 -38
- package/dist/core/hooks/backup-schedule.d.ts +34 -0
- package/dist/core/hooks/backup-schedule.d.ts.map +1 -0
- package/dist/core/hooks/backup-schedule.js +114 -0
- package/dist/core/hooks/cli.js +61 -18
- package/dist/core/hooks/codex-wsl-bridge.d.ts +2 -0
- package/dist/core/hooks/codex-wsl-bridge.d.ts.map +1 -1
- package/dist/core/hooks/codex-wsl-bridge.js +18 -4
- package/dist/core/hooks/index.d.ts +1 -0
- package/dist/core/hooks/index.d.ts.map +1 -1
- package/dist/core/hooks/index.js +1 -0
- package/dist/core/hooks/prompt-context/contract.d.ts +61 -0
- package/dist/core/hooks/prompt-context/contract.d.ts.map +1 -0
- package/dist/core/hooks/prompt-context/contract.js +102 -0
- package/dist/core/hooks/prompt-context/index.d.ts +5 -0
- package/dist/core/hooks/prompt-context/index.d.ts.map +1 -0
- package/dist/core/hooks/prompt-context/index.js +2 -0
- package/dist/core/hooks/prompt-context/runner.d.ts +24 -0
- package/dist/core/hooks/prompt-context/runner.d.ts.map +1 -0
- package/dist/core/hooks/prompt-context/runner.js +249 -0
- package/dist/core/qa-artifacts.d.ts +3 -0
- package/dist/core/qa-artifacts.d.ts.map +1 -1
- package/dist/core/qa-artifacts.js +25 -3
- package/dist/core/resources/contract.d.ts +69 -3
- package/dist/core/resources/contract.d.ts.map +1 -1
- package/dist/core/resources/contract.js +1 -1
- package/dist/core/resources/index.d.ts +1 -0
- package/dist/core/resources/index.d.ts.map +1 -1
- package/dist/core/resources/index.js +1 -0
- package/dist/core/resources/native.d.ts +36 -0
- package/dist/core/resources/native.d.ts.map +1 -0
- package/dist/core/resources/native.js +418 -0
- package/dist/core/resources/sampler.d.ts +2 -0
- package/dist/core/resources/sampler.d.ts.map +1 -1
- package/dist/core/resources/sampler.js +31 -1
- package/dist/core/resources/status.d.ts +61 -0
- package/dist/core/resources/status.d.ts.map +1 -0
- package/dist/core/resources/status.js +536 -0
- package/dist/core/resources/system.d.ts +27 -0
- package/dist/core/resources/system.d.ts.map +1 -0
- package/dist/core/resources/system.js +439 -0
- package/dist/core/semantic/contract.d.ts +13 -13
- package/dist/core/semantic/service.d.ts +2 -0
- package/dist/core/semantic/service.d.ts.map +1 -1
- package/dist/core/semantic/service.js +27 -4
- package/dist/core/storage/builtins.js +41 -0
- package/dist/core/supervisor/contract.d.ts +41 -3
- package/dist/core/supervisor/contract.d.ts.map +1 -1
- package/dist/core/supervisor/contract.js +3 -2
- package/dist/core/supervisor/findings.d.ts +3 -0
- package/dist/core/supervisor/findings.d.ts.map +1 -1
- package/dist/core/supervisor/findings.js +88 -0
- package/dist/core/supervisor/history.d.ts.map +1 -1
- package/dist/core/supervisor/history.js +14 -0
- package/dist/core/supervisor/ledger-state.d.ts +55 -0
- package/dist/core/supervisor/ledger-state.d.ts.map +1 -0
- package/dist/core/supervisor/ledger-state.js +0 -0
- package/dist/core/supervisor/service.d.ts.map +1 -1
- package/dist/core/supervisor/service.js +120 -7
- package/dist/core/supervisor/storage.d.ts +3 -1
- package/dist/core/supervisor/storage.d.ts.map +1 -1
- package/dist/core/supervisor/storage.js +4 -0
- package/dist/core/workflow/admission.d.ts +3 -0
- package/dist/core/workflow/admission.d.ts.map +1 -1
- package/dist/core/workflow/admission.js +28 -16
- package/dist/core/workflow/engine.js +2 -2
- package/dist/core/workflow/live-session-v3.d.ts.map +1 -1
- package/dist/core/workflow/live-session-v3.js +6 -1
- package/dist/core/workflow/proof.d.ts.map +1 -1
- package/dist/core/workflow/proof.js +4 -3
- package/dist/lib/browser/capture-fidelity.d.ts +7 -0
- package/dist/lib/browser/capture-fidelity.d.ts.map +1 -1
- package/dist/lib/browser/capture-fidelity.js +17 -0
- package/dist/lib/browser/client.d.ts +9 -0
- package/dist/lib/browser/client.d.ts.map +1 -1
- package/dist/lib/browser/client.js +85 -37
- package/dist/lib/browser/index.d.ts +3 -1
- package/dist/lib/browser/index.d.ts.map +1 -1
- package/dist/lib/browser/index.js +2 -1
- package/dist/lib/browser/page-review-budget.d.ts +13 -0
- package/dist/lib/browser/page-review-budget.d.ts.map +1 -0
- package/dist/lib/browser/page-review-budget.js +251 -0
- package/dist/lib/browser/page-review-contracts.d.ts +127 -0
- package/dist/lib/browser/page-review-contracts.d.ts.map +1 -0
- package/dist/lib/browser/page-review-contracts.js +5 -0
- package/dist/lib/browser/page-review-evidence.d.ts +48 -0
- package/dist/lib/browser/page-review-evidence.d.ts.map +1 -0
- package/dist/lib/browser/page-review-evidence.js +340 -0
- package/dist/lib/browser/page-review-judge.d.ts +1 -1
- package/dist/lib/browser/page-review-judge.d.ts.map +1 -1
- package/dist/lib/browser/page-review-judge.js +46 -38
- package/dist/lib/browser/page-review-pack.d.ts +44 -22
- package/dist/lib/browser/page-review-pack.d.ts.map +1 -1
- package/dist/lib/browser/page-review-pack.js +116 -36
- package/dist/lib/browser/page-review-source.d.ts +12 -0
- package/dist/lib/browser/page-review-source.d.ts.map +1 -0
- package/dist/lib/browser/page-review-source.js +22 -0
- package/dist/lib/browser/qa-run-contracts.d.ts +35 -9
- package/dist/lib/browser/qa-run-contracts.d.ts.map +1 -1
- package/dist/lib/browser/qa-run-contracts.js +48 -14
- package/dist/lib/browser/qa-run.d.ts.map +1 -1
- package/dist/lib/browser/qa-run.js +215 -68
- package/dist/lib/browser/qa-snapshot.d.ts +6 -0
- package/dist/lib/browser/qa-snapshot.d.ts.map +1 -1
- package/dist/lib/browser/qa-snapshot.js +259 -48
- package/dist/lib/browser/review-capture-transaction.d.ts +13 -0
- package/dist/lib/browser/review-capture-transaction.d.ts.map +1 -0
- package/dist/lib/browser/review-capture-transaction.js +79 -0
- package/dist/lib/browser/standalone-html.d.ts.map +1 -1
- package/dist/lib/browser/standalone-html.js +4 -2
- package/dist/lib/browser/thumbnail-association.d.ts +34 -0
- package/dist/lib/browser/thumbnail-association.d.ts.map +1 -0
- package/dist/lib/browser/thumbnail-association.js +158 -0
- package/dist/lib/browser/tiling.d.ts +30 -0
- package/dist/lib/browser/tiling.d.ts.map +1 -1
- package/dist/lib/browser/tiling.js +38 -0
- package/dist/lib/docs-metadata-managed.d.ts.map +1 -1
- package/dist/lib/docs-metadata-managed.js +4 -1
- package/dist/lib/identities/assume.d.ts.map +1 -1
- package/dist/lib/identities/assume.js +7 -1
- package/dist/lib/instructions/templates.d.ts.map +1 -1
- package/dist/lib/instructions/templates.js +1 -0
- package/dist/lib/local-file-url.d.ts +12 -0
- package/dist/lib/local-file-url.d.ts.map +1 -1
- package/dist/lib/local-file-url.js +16 -2
- package/dist/lib/tunnel/state.d.ts +19 -4
- package/dist/lib/tunnel/state.d.ts.map +1 -1
- package/dist/lib/tunnel/state.js +53 -13
- package/package.json +7 -4
- package/schemas/config.schema.json +69 -2
- package/src/commands/agents.ts +42 -9
- package/src/commands/artifacts.ts +130 -6
- package/src/commands/backup.ts +287 -34
- package/src/commands/browse.ts +564 -267
- package/src/commands/diagnostics.ts +6 -3
- package/src/commands/ledger-v3.ts +20 -3
- package/src/commands/qa-run.ts +37 -3
- package/src/commands/qa-status.ts +11 -2
- package/src/commands/resources.ts +23 -3
- package/src/commands/review-pack.ts +165 -62
- package/src/commands/tunnel.ts +2 -1
- package/src/core/agents/canonical-emit.ts +4 -2
- package/src/core/agents/cli.ts +14 -6
- package/src/core/agents/live-authority-v3.ts +7 -3
- package/src/core/agents/live-lifecycle-v3.ts +2 -2
- package/src/core/agents/render/prompt-context.ts +68 -14
- package/src/core/agents/session-finalizer-v3.ts +2 -2
- package/src/core/agents/state/live-coordination-view.ts +4 -4
- package/src/core/agents/state/names.ts +92 -7
- package/src/core/agents/state/stale-sweep.ts +22 -8
- package/src/core/agents/turn-elapsed.ts +135 -0
- package/src/core/artifacts/activity.ts +71 -0
- package/src/core/artifacts/delivery-card.ts +375 -0
- package/src/core/artifacts/index.ts +385 -91
- package/src/core/artifacts/mutation-lock.ts +106 -0
- package/src/core/backup/host-snapshot.ts +76 -0
- package/src/core/config.ts +66 -13
- package/src/core/diagnostics/advice.ts +148 -120
- package/src/core/diagnostics/bundle.ts +138 -52
- package/src/core/diagnostics/comparison.ts +6 -2
- package/src/core/diagnostics/contract.ts +39 -42
- package/src/core/diagnostics/index.ts +2 -0
- package/src/core/diagnostics/pressure-contract.ts +294 -0
- package/src/core/diagnostics/pressure.ts +1361 -0
- package/src/core/diagnostics/replay.ts +116 -11
- package/src/core/events/v3/adapter-id.ts +12 -0
- package/src/core/events/v3/archive-retention.ts +3 -6
- package/src/core/events/v3/bootstrap.ts +163 -16
- package/src/core/events/v3/capabilities.ts +75 -25
- package/src/core/events/v3/capability-drift.ts +2 -2
- package/src/core/events/v3/control-witness.ts +172 -41
- package/src/core/events/v3/control.ts +34 -2
- package/src/core/events/v3/index.ts +3 -0
- package/src/core/events/v3/live-feed.ts +23 -5
- package/src/core/events/v3/live-routing.ts +14 -2
- package/src/core/events/v3/producers/command-recorder.ts +4 -4
- package/src/core/events/v3/producers/coordination-recorder.ts +4 -4
- package/src/core/events/v3/producers/hook-base.ts +21 -16
- package/src/core/events/v3/producers/hook.ts +21 -4
- package/src/core/events/v3/producers/intake.ts +6 -6
- package/src/core/events/v3/producers/producer-state-observer.ts +2 -1
- package/src/core/events/v3/producers/recorder.ts +50 -21
- package/src/core/events/v3/reader.ts +14 -13
- package/src/core/events/v3/runtime-build.ts +3 -2
- package/src/core/events/v3/runtime-telemetry-capabilities.ts +3 -3
- package/src/core/events/v3/turn-telemetry.ts +4 -4
- package/src/core/hooks/adapter/output.ts +50 -36
- package/src/core/hooks/backup-schedule.ts +147 -0
- package/src/core/hooks/cli.ts +65 -14
- package/src/core/hooks/codex-wsl-bridge.ts +21 -5
- package/src/core/hooks/index.ts +1 -0
- package/src/core/hooks/prompt-context/contract.ts +177 -0
- package/src/core/hooks/prompt-context/index.ts +22 -0
- package/src/core/hooks/prompt-context/runner.ts +303 -0
- package/src/core/qa-artifacts.ts +29 -3
- package/src/core/resources/contract.ts +76 -3
- package/src/core/resources/index.ts +1 -0
- package/src/core/resources/native.ts +481 -0
- package/src/core/resources/sampler.ts +46 -1
- package/src/core/resources/status.ts +717 -0
- package/src/core/resources/system.ts +530 -0
- package/src/core/semantic/service.ts +37 -3
- package/src/core/storage/builtins.ts +41 -0
- package/src/core/supervisor/contract.ts +45 -3
- package/src/core/supervisor/findings.ts +159 -0
- package/src/core/supervisor/history.ts +15 -0
- package/src/core/supervisor/ledger-state.ts +0 -0
- package/src/core/supervisor/service.ts +133 -5
- package/src/core/supervisor/storage.ts +7 -0
- package/src/core/workflow/admission.ts +33 -17
- package/src/core/workflow/engine.ts +2 -2
- package/src/core/workflow/live-session-v3.ts +6 -8
- package/src/core/workflow/proof.ts +4 -3
- package/src/lib/browser/capture-fidelity.ts +23 -0
- package/src/lib/browser/client.ts +96 -44
- package/src/lib/browser/index.ts +15 -1
- package/src/lib/browser/page-review-budget.ts +309 -0
- package/src/lib/browser/page-review-contracts.ts +129 -0
- package/src/lib/browser/page-review-evidence.ts +465 -0
- package/src/lib/browser/page-review-judge.ts +56 -40
- package/src/lib/browser/page-review-pack.ts +181 -43
- package/src/lib/browser/page-review-source.ts +30 -0
- package/src/lib/browser/qa-run-contracts.ts +62 -18
- package/src/lib/browser/qa-run.ts +259 -71
- package/src/lib/browser/qa-snapshot.ts +289 -48
- package/src/lib/browser/review-capture-transaction.ts +106 -0
- package/src/lib/browser/standalone-html.ts +4 -2
- package/src/lib/browser/thumbnail-association.ts +190 -0
- package/src/lib/browser/tiling.ts +63 -0
- package/src/lib/docs-metadata-managed.ts +4 -1
- package/src/lib/identities/assume.ts +12 -4
- package/src/lib/instructions/templates.ts +1 -0
- package/src/lib/local-file-url.ts +17 -2
- package/src/lib/tunnel/state.ts +70 -13
package/src/commands/browse.ts
CHANGED
|
@@ -8,6 +8,7 @@ import { resolveBinName } from "../core/config.ts";
|
|
|
8
8
|
import {
|
|
9
9
|
CAPTURE_FIDELITY_MISMATCH_THRESHOLD,
|
|
10
10
|
compareBand,
|
|
11
|
+
cropNativePng,
|
|
11
12
|
decideFidelity,
|
|
12
13
|
type FidelityProbe,
|
|
13
14
|
pngDimensions,
|
|
@@ -43,8 +44,19 @@ import {
|
|
|
43
44
|
writeNetscapeCookieFile,
|
|
44
45
|
wslHeadedLaunchArgs,
|
|
45
46
|
} from "../lib/browser/index.ts";
|
|
47
|
+
import {
|
|
48
|
+
allocateTileBudget,
|
|
49
|
+
validatePageReviewAllocation,
|
|
50
|
+
} from "../lib/browser/page-review-budget.ts";
|
|
51
|
+
import {
|
|
52
|
+
PAGE_REVIEW_CAPTURE_PLAN_SCHEMA,
|
|
53
|
+
type PageReviewCapturePlan,
|
|
54
|
+
type PageReviewContextAllocation,
|
|
55
|
+
type PageReviewSourceEvidence,
|
|
56
|
+
} from "../lib/browser/page-review-contracts.ts";
|
|
46
57
|
import {
|
|
47
58
|
findPackTile,
|
|
59
|
+
gateHitsFromEnvelope,
|
|
48
60
|
type PageReviewCaptureFidelity,
|
|
49
61
|
type PageReviewContextRecord,
|
|
50
62
|
type PageReviewExpandedTileRecord,
|
|
@@ -53,6 +65,7 @@ import {
|
|
|
53
65
|
writePackContext,
|
|
54
66
|
writePackExpandedTile,
|
|
55
67
|
} from "../lib/browser/page-review-pack.ts";
|
|
68
|
+
import { retainPageReviewSourceIdentity } from "../lib/browser/page-review-source.ts";
|
|
56
69
|
import {
|
|
57
70
|
buildQaManifest,
|
|
58
71
|
classifySignatures,
|
|
@@ -71,10 +84,23 @@ import {
|
|
|
71
84
|
rubricDigest,
|
|
72
85
|
} from "../lib/browser/qa-reuse.ts";
|
|
73
86
|
import { loadQaSnapshot, resolveQaBaseline, saveQaSnapshot } from "../lib/browser/qa-snapshot.ts";
|
|
87
|
+
import {
|
|
88
|
+
allocateCaptureReservation,
|
|
89
|
+
assertGatePlanStable,
|
|
90
|
+
CaptureSourceChanged,
|
|
91
|
+
runCaptureTransaction,
|
|
92
|
+
writeCaptureTransaction,
|
|
93
|
+
} from "../lib/browser/review-capture-transaction.ts";
|
|
74
94
|
import {
|
|
75
95
|
type BrowserSessionServer,
|
|
76
96
|
startBrowserSessionServer,
|
|
77
97
|
} from "../lib/browser/session-control.ts";
|
|
98
|
+
import {
|
|
99
|
+
type CaptureFingerprint,
|
|
100
|
+
captureThumbnailFingerprint,
|
|
101
|
+
registerCaptureThumbnail,
|
|
102
|
+
} from "../lib/browser/thumbnail-association.ts";
|
|
103
|
+
import { reviewCandidateRects } from "../lib/browser/tiling.ts";
|
|
78
104
|
import {
|
|
79
105
|
type DiffResult,
|
|
80
106
|
diffAgainstBaseline,
|
|
@@ -108,6 +134,16 @@ const DEFAULT_STORE = resolve(homedir(), ".cache", "harnery", "cookies.json");
|
|
|
108
134
|
const FALLBACK_OUT_PREFIX = resolve(homedir(), ".cache", "harnery", "browse", "last");
|
|
109
135
|
|
|
110
136
|
interface BrowseOpts {
|
|
137
|
+
reviewPackPlan?: boolean;
|
|
138
|
+
reviewPackAllocation?: string;
|
|
139
|
+
reviewPackReservation?: string;
|
|
140
|
+
captureTransactionAllocation?: PageReviewContextAllocation;
|
|
141
|
+
captureTransaction?: {
|
|
142
|
+
attempts: Array<{ attempt: number; status: string; source_digest?: string; error?: string }>;
|
|
143
|
+
source_digest?: string;
|
|
144
|
+
};
|
|
145
|
+
capturePlanResult?: PageReviewCapturePlan;
|
|
146
|
+
captureSourceEvidence?: PageReviewSourceEvidence;
|
|
111
147
|
out?: string;
|
|
112
148
|
// Commander expands `--no-X` into `opts.x = false` (default true), not
|
|
113
149
|
// `opts.noX`. So `--no-screenshot` toggles `screenshot`, `--no-full-page`
|
|
@@ -236,7 +272,7 @@ interface BrowseOpts {
|
|
|
236
272
|
reviewPack?: string;
|
|
237
273
|
reviewPackContext?: string;
|
|
238
274
|
reviewPackScope?: string[];
|
|
239
|
-
|
|
275
|
+
|
|
240
276
|
/** Re-capture ONE existing tile of --review-pack-context at --device-scale-factor. */
|
|
241
277
|
reviewPackExpand?: string;
|
|
242
278
|
/** Gate-hit rectangles (`x,y,w,h` strings) whose bands are captured past the tile cap. */
|
|
@@ -725,6 +761,19 @@ export function registerBrowseCommand(
|
|
|
725
761
|
"vision call. A judge stage (`review-pack judge`, or qa-run) reviews the tiles from disk " +
|
|
726
762
|
"after every browser has closed. Tiling knobs are the --check-critique-* flags.",
|
|
727
763
|
)
|
|
764
|
+
.option(
|
|
765
|
+
"--review-pack-plan",
|
|
766
|
+
"Emit all native capture candidates and gate associations without capturing tiles. " +
|
|
767
|
+
"With --out, retain private source identity inputs beside the output for diagnostics.",
|
|
768
|
+
)
|
|
769
|
+
.option(
|
|
770
|
+
"--review-pack-allocation <json-file>",
|
|
771
|
+
"Capture exactly the selected IDs in a verified context allocation.",
|
|
772
|
+
)
|
|
773
|
+
.option(
|
|
774
|
+
"--review-pack-reservation <json-file>",
|
|
775
|
+
"Reserve the preliminary allocation's tile count, then rerun supplied gates and allocate against the same page before capture. Full source checks and native coverage remain required.",
|
|
776
|
+
)
|
|
728
777
|
.option(
|
|
729
778
|
"--review-pack-context <id>",
|
|
730
779
|
"Context id inside the pack (default <viewport>-<theme>-<state> from --viewport, " +
|
|
@@ -736,11 +785,6 @@ export function registerBrowseCommand(
|
|
|
736
785
|
(value: string, previous: string[] = []) => [...previous, value],
|
|
737
786
|
[] as string[],
|
|
738
787
|
)
|
|
739
|
-
.option(
|
|
740
|
-
"--no-review-pack-bands",
|
|
741
|
-
"Skip full-page bands in the pack and keep only --review-pack-scope tiles (a scoped " +
|
|
742
|
-
"visual pass).",
|
|
743
|
-
)
|
|
744
788
|
.option(
|
|
745
789
|
"--review-pack-expand <tile-id>",
|
|
746
790
|
"Instead of capturing a context, re-render ONE existing tile of --review-pack-context " +
|
|
@@ -749,9 +793,9 @@ export function registerBrowseCommand(
|
|
|
749
793
|
)
|
|
750
794
|
.option(
|
|
751
795
|
"--review-pack-hit-rect <x,y,w,h>",
|
|
752
|
-
"Document-space rectangle (integer px) a deterministic gate already flagged.
|
|
753
|
-
"
|
|
754
|
-
`
|
|
796
|
+
"Document-space rectangle (integer px) a deterministic gate already flagged. " +
|
|
797
|
+
"Intersecting bands are prioritized within the hard tile cap " +
|
|
798
|
+
`(repeatable, at most ${REVIEW_PACK_HIT_RECT_MAX}).`,
|
|
755
799
|
(value: string, previous: string[] = []) => [...previous, value],
|
|
756
800
|
[] as string[],
|
|
757
801
|
)
|
|
@@ -815,6 +859,17 @@ async function runBrowse(
|
|
|
815
859
|
const headed = opts.login || opts.headed;
|
|
816
860
|
const viewport = parseViewport(opts.viewport ?? "desktop");
|
|
817
861
|
const deviceScaleFactor = parseDeviceScaleFactor(opts.deviceScaleFactor);
|
|
862
|
+
if (opts.reviewPackAllocation && !opts.reviewPack) {
|
|
863
|
+
throw new Error("--review-pack-allocation requires --review-pack <dir>.");
|
|
864
|
+
}
|
|
865
|
+
if (
|
|
866
|
+
opts.reviewPackReservation &&
|
|
867
|
+
(!opts.reviewPack || opts.reviewPackAllocation || opts.reviewPackExpand || opts.checkCritique)
|
|
868
|
+
) {
|
|
869
|
+
throw new Error(
|
|
870
|
+
"--review-pack-reservation requires an ordinary --review-pack capture without allocation, expansion or inline critique.",
|
|
871
|
+
);
|
|
872
|
+
}
|
|
818
873
|
if (opts.reviewPackExpand !== undefined) {
|
|
819
874
|
if (opts.reviewPack === undefined || opts.reviewPackContext === undefined) {
|
|
820
875
|
throw new Error(
|
|
@@ -905,126 +960,216 @@ async function runBrowse(
|
|
|
905
960
|
evalResult = await browser.evaluate<unknown>(opts.evaluate);
|
|
906
961
|
}
|
|
907
962
|
|
|
963
|
+
if (opts.reviewPackPlan || opts.reviewPack) await browser.waitForReviewReady();
|
|
964
|
+
|
|
908
965
|
// Run visibility checks AFTER any --batch interactions but BEFORE the
|
|
909
966
|
// screenshot. Annotation injection happens between sampling and capture
|
|
910
967
|
// so the boxes show on the saved PNG; they're cleared post-screenshot
|
|
911
968
|
// so the live profile state isn't polluted.
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
if (opts.
|
|
918
|
-
await browser.
|
|
969
|
+
const collectGates = async (attempt = 0) => {
|
|
970
|
+
const before = opts.reviewPackReservation
|
|
971
|
+
? await buildReviewCapturePlan(browser, opts)
|
|
972
|
+
: undefined;
|
|
973
|
+
let visibility: VisibilityResult[] | undefined;
|
|
974
|
+
if (opts.checkVisible && opts.checkVisible.length > 0) {
|
|
975
|
+
visibility = await browser.checkVisibility(opts.checkVisible, {
|
|
976
|
+
sampleGrid: Number.parseInt(opts.checkVisibleSampleGrid ?? "3", 10),
|
|
977
|
+
});
|
|
978
|
+
if (opts.checkVisibleAnnotate !== false && !opts.reviewPackPlan && !opts.reviewPack) {
|
|
979
|
+
await browser.annotateVisibility(visibility);
|
|
980
|
+
}
|
|
919
981
|
}
|
|
920
|
-
}
|
|
921
982
|
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
983
|
+
let widths: WidthResult[] | undefined;
|
|
984
|
+
if (opts.checkWidth && opts.checkWidth.length > 0) {
|
|
985
|
+
widths = await browser.checkWidth(opts.checkWidth);
|
|
986
|
+
}
|
|
987
|
+
let overflow: OverflowResult | undefined;
|
|
988
|
+
if (opts.checkOverflow) {
|
|
989
|
+
overflow = await browser.checkOverflow();
|
|
990
|
+
}
|
|
991
|
+
let runts: RuntsResult | undefined;
|
|
992
|
+
if (opts.checkRunts) {
|
|
993
|
+
runts = await browser.checkRunts({
|
|
994
|
+
scope: typeof opts.checkRunts === "string" ? opts.checkRunts : null,
|
|
995
|
+
minChars: Number.parseInt(opts.checkRuntsMinChars ?? "40", 10),
|
|
996
|
+
});
|
|
997
|
+
}
|
|
998
|
+
const hasLayoutLint =
|
|
999
|
+
(opts.checkAlign?.length ?? 0) > 0 ||
|
|
1000
|
+
(opts.checkGap?.length ?? 0) > 0 ||
|
|
1001
|
+
(opts.checkClip?.length ?? 0) > 0 ||
|
|
1002
|
+
(opts.checkOverlap?.length ?? 0) > 0 ||
|
|
1003
|
+
(opts.checkCrowd?.length ?? 0) > 0;
|
|
1004
|
+
let layoutLint: LayoutLintResult | undefined;
|
|
1005
|
+
if (hasLayoutLint) {
|
|
1006
|
+
layoutLint = await browser.checkLayoutLint({
|
|
1007
|
+
align: (opts.checkAlign ?? []).map((selector) => ({
|
|
1008
|
+
selector,
|
|
1009
|
+
axis: alignAxis,
|
|
1010
|
+
tolerancePx: alignThreshold,
|
|
1011
|
+
})),
|
|
1012
|
+
gap: (opts.checkGap ?? []).map((selector) => ({
|
|
1013
|
+
selector,
|
|
1014
|
+
axis: gapAxis,
|
|
1015
|
+
tolerancePx: gapThreshold,
|
|
1016
|
+
expectedGapPx: gapExpected,
|
|
1017
|
+
})),
|
|
1018
|
+
clip: (opts.checkClip ?? []).map((selector) => ({
|
|
1019
|
+
selector,
|
|
1020
|
+
tolerancePx: clipThreshold,
|
|
1021
|
+
})),
|
|
1022
|
+
overlap: (opts.checkOverlap ?? []).map((selector) => ({
|
|
1023
|
+
selector,
|
|
1024
|
+
tolerancePx: overlapThreshold,
|
|
1025
|
+
})),
|
|
1026
|
+
crowd: (opts.checkCrowd ?? []).map((selector) => ({
|
|
1027
|
+
selector,
|
|
1028
|
+
minGapPx: crowdMin,
|
|
1029
|
+
})),
|
|
1030
|
+
});
|
|
1031
|
+
}
|
|
1032
|
+
let hit: TargetSizeResult[] | undefined;
|
|
1033
|
+
if (opts.checkHit && opts.checkHit.length > 0) {
|
|
1034
|
+
hit = await browser.checkTargetSize(opts.checkHit, hitProfile);
|
|
1035
|
+
}
|
|
1036
|
+
const hasContentChecks =
|
|
1037
|
+
opts.checkPlaceholder !== undefined ||
|
|
1038
|
+
opts.checkImages !== undefined ||
|
|
1039
|
+
opts.checkTruncation !== undefined ||
|
|
1040
|
+
opts.checkContrast !== undefined;
|
|
1041
|
+
let content: ContentChecksResult | undefined;
|
|
1042
|
+
if (hasContentChecks) {
|
|
1043
|
+
content = await browser.checkContent({
|
|
1044
|
+
placeholder:
|
|
1045
|
+
opts.checkPlaceholder !== undefined
|
|
1046
|
+
? { scope: contentScope(opts.checkPlaceholder) }
|
|
1047
|
+
: null,
|
|
1048
|
+
image:
|
|
1049
|
+
opts.checkImages !== undefined
|
|
1050
|
+
? {
|
|
1051
|
+
scope: contentScope(opts.checkImages),
|
|
1052
|
+
tolerance: parseNonNegativeNumber(
|
|
1053
|
+
opts.checkImagesTolerance ?? "0.1",
|
|
1054
|
+
"--check-images-tolerance",
|
|
1055
|
+
),
|
|
1056
|
+
}
|
|
1057
|
+
: null,
|
|
1058
|
+
truncation:
|
|
1059
|
+
opts.checkTruncation !== undefined
|
|
1060
|
+
? {
|
|
1061
|
+
scope: contentScope(opts.checkTruncation),
|
|
1062
|
+
tolerance: parseNonNegativeNumber(
|
|
1063
|
+
opts.checkTruncationTolerance ?? "2",
|
|
1064
|
+
"--check-truncation-tolerance",
|
|
1065
|
+
),
|
|
1066
|
+
}
|
|
1067
|
+
: null,
|
|
1068
|
+
contrast:
|
|
1069
|
+
opts.checkContrast !== undefined ? { scope: contentScope(opts.checkContrast) } : null,
|
|
1070
|
+
});
|
|
1071
|
+
}
|
|
1072
|
+
// Diff-aware QA planning. Signature capture must also happen BEFORE any
|
|
1073
|
+
// annotation overlays are injected — an annotation box is a DOM change.
|
|
1074
|
+
let qaPlan: QaPlanReport | undefined;
|
|
1075
|
+
let qaCapture: QaCaptureState | undefined;
|
|
1076
|
+
if (opts.qaPlan || opts.qaSnapshot) {
|
|
1077
|
+
const qa = await runQaPlanning(browser, url, navResult.url, opts);
|
|
1078
|
+
qaPlan = qa.report;
|
|
1079
|
+
qaCapture = qa.capture;
|
|
1080
|
+
if (qaPlan?.manifest) {
|
|
1081
|
+
const m = qaPlan.manifest;
|
|
1082
|
+
emit.log(
|
|
1083
|
+
`qa-plan: class=${m.change_class} scopes=${m.scopes.map((s) => s.selector).join(",") || "-"} ` +
|
|
1084
|
+
`contexts=${m.contexts.length} model-calls<=${m.predicted.model_calls_ceiling} baseline=${m.baseline_source}`,
|
|
1085
|
+
"info",
|
|
1086
|
+
);
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
let asserts: AssertResult[] | undefined;
|
|
1090
|
+
if (opts.assert && opts.assert.length > 0) {
|
|
1091
|
+
const specs = opts.assert.map(parseAssertSpec);
|
|
1092
|
+
asserts = await browser.checkAsserts(specs);
|
|
1093
|
+
}
|
|
1094
|
+
if (opts.reviewPackPlan || opts.reviewPackReservation) {
|
|
1095
|
+
const gateEnvelope: Record<string, unknown> = { runts, overflow, hit, ...layoutLint };
|
|
1096
|
+
if (content) assignContent(gateEnvelope, content);
|
|
1097
|
+
opts.capturePlanResult = await buildReviewCapturePlan(browser, opts);
|
|
1098
|
+
const hits = gateHitsFromEnvelope(gateEnvelope, Number.POSITIVE_INFINITY);
|
|
1099
|
+
for (const candidate of opts.capturePlanResult.candidates) {
|
|
1100
|
+
const r = candidate.rect;
|
|
1101
|
+
candidate.gate_hits = hits.flatMap((hit, index) => {
|
|
1102
|
+
const dpr = opts.capturePlanResult!.dpr;
|
|
1103
|
+
const h = {
|
|
1104
|
+
x: hit.rect.x * dpr,
|
|
1105
|
+
y: hit.rect.y * dpr,
|
|
1106
|
+
width: Math.max(1, hit.rect.width * dpr),
|
|
1107
|
+
height: Math.max(1, hit.rect.height * dpr),
|
|
1108
|
+
};
|
|
1109
|
+
return r.x < h.x + h.width &&
|
|
1110
|
+
r.x + r.width > h.x &&
|
|
1111
|
+
r.y < h.y + h.height &&
|
|
1112
|
+
r.y + r.height > h.y
|
|
1113
|
+
? [{ check_id: `${hit.rule}:${index + 1}`, severity: "high" as const }]
|
|
1114
|
+
: [];
|
|
1115
|
+
});
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
if (opts.reviewPackReservation) {
|
|
1120
|
+
const prefix = opts.out ?? resolve(opts.reviewPack!, opts.reviewPackContext ?? "capture");
|
|
1121
|
+
mkdirSync(dirname(prefix), { recursive: true, mode: 0o700 });
|
|
1122
|
+
writeFileSync(
|
|
1123
|
+
`${prefix}.attempt-${attempt}.gates.json`,
|
|
1124
|
+
JSON.stringify(
|
|
1125
|
+
{
|
|
1126
|
+
attempt,
|
|
1127
|
+
source_before: before?.source_digest,
|
|
1128
|
+
source_after: opts.capturePlanResult?.source_digest,
|
|
1129
|
+
source_evidence: opts.captureSourceEvidence,
|
|
1130
|
+
visibility,
|
|
1131
|
+
widths,
|
|
1132
|
+
overflow,
|
|
1133
|
+
runts,
|
|
1134
|
+
layoutLint,
|
|
1135
|
+
hit,
|
|
1136
|
+
content,
|
|
1137
|
+
asserts,
|
|
1138
|
+
},
|
|
1139
|
+
null,
|
|
1140
|
+
2,
|
|
1141
|
+
),
|
|
1142
|
+
{ mode: 0o600 },
|
|
1025
1143
|
);
|
|
1026
1144
|
}
|
|
1027
|
-
|
|
1145
|
+
if (before) assertGatePlanStable(before, opts.capturePlanResult!);
|
|
1146
|
+
return {
|
|
1147
|
+
visibility,
|
|
1148
|
+
widths,
|
|
1149
|
+
overflow,
|
|
1150
|
+
runts,
|
|
1151
|
+
layoutLint,
|
|
1152
|
+
hit,
|
|
1153
|
+
content,
|
|
1154
|
+
qaPlan,
|
|
1155
|
+
qaCapture,
|
|
1156
|
+
asserts,
|
|
1157
|
+
};
|
|
1158
|
+
};
|
|
1159
|
+
let {
|
|
1160
|
+
visibility,
|
|
1161
|
+
widths,
|
|
1162
|
+
overflow,
|
|
1163
|
+
runts,
|
|
1164
|
+
layoutLint,
|
|
1165
|
+
hit,
|
|
1166
|
+
content,
|
|
1167
|
+
qaPlan,
|
|
1168
|
+
qaCapture,
|
|
1169
|
+
asserts,
|
|
1170
|
+
}: Partial<Awaited<ReturnType<typeof collectGates>>> = opts.reviewPackReservation
|
|
1171
|
+
? {}
|
|
1172
|
+
: await collectGates();
|
|
1028
1173
|
// Vision critique. Capture tiles BEFORE any annotation overlays are injected
|
|
1029
1174
|
// so the model sees the real page, not our boxes.
|
|
1030
1175
|
let critique: CritiqueResult | undefined;
|
|
@@ -1095,6 +1240,62 @@ async function runBrowse(
|
|
|
1095
1240
|
let reviewPack: ReviewPackReport | undefined;
|
|
1096
1241
|
if (opts.reviewPack !== undefined && opts.reviewPackExpand !== undefined) {
|
|
1097
1242
|
reviewPack = await expandReviewPackTile(browser, opts, deviceScaleFactor ?? 1);
|
|
1243
|
+
} else if (opts.reviewPackReservation) {
|
|
1244
|
+
const reservation = JSON.parse(readFileSync(resolve(opts.reviewPackReservation), "utf8"));
|
|
1245
|
+
opts.captureTransaction = { attempts: [] };
|
|
1246
|
+
const persistTransaction = () => {
|
|
1247
|
+
const prefix = opts.out ?? resolve(opts.reviewPack!, opts.reviewPackContext ?? "capture");
|
|
1248
|
+
mkdirSync(dirname(prefix), { recursive: true, mode: 0o700 });
|
|
1249
|
+
writeFileSync(
|
|
1250
|
+
`${prefix}.capture-transaction.json`,
|
|
1251
|
+
JSON.stringify(opts.captureTransaction, null, 2),
|
|
1252
|
+
{ mode: 0o600 },
|
|
1253
|
+
);
|
|
1254
|
+
};
|
|
1255
|
+
reviewPack = await runCaptureTransaction(
|
|
1256
|
+
async (attempt) => {
|
|
1257
|
+
await browser.waitForReviewReady();
|
|
1258
|
+
({
|
|
1259
|
+
visibility,
|
|
1260
|
+
widths,
|
|
1261
|
+
overflow,
|
|
1262
|
+
runts,
|
|
1263
|
+
layoutLint,
|
|
1264
|
+
hit,
|
|
1265
|
+
content,
|
|
1266
|
+
qaPlan,
|
|
1267
|
+
qaCapture,
|
|
1268
|
+
asserts,
|
|
1269
|
+
} = await collectGates(attempt));
|
|
1270
|
+
opts.captureTransactionAllocation = allocateCaptureReservation(
|
|
1271
|
+
reservation,
|
|
1272
|
+
opts.capturePlanResult!,
|
|
1273
|
+
);
|
|
1274
|
+
opts.captureTransaction!.attempts.push({
|
|
1275
|
+
attempt,
|
|
1276
|
+
status: "gated",
|
|
1277
|
+
source_digest: opts.capturePlanResult!.source_digest,
|
|
1278
|
+
});
|
|
1279
|
+
persistTransaction();
|
|
1280
|
+
},
|
|
1281
|
+
async () => {
|
|
1282
|
+
const captured = await captureReviewPackContext(browser, url, navResult, opts, qaCapture);
|
|
1283
|
+
opts.captureTransaction!.source_digest =
|
|
1284
|
+
opts.captureTransactionAllocation!.plan.source_digest;
|
|
1285
|
+
opts.captureTransaction!.attempts.at(-1)!.status = "captured";
|
|
1286
|
+
persistTransaction();
|
|
1287
|
+
return captured;
|
|
1288
|
+
},
|
|
1289
|
+
(attempt, error) => {
|
|
1290
|
+
opts.captureTransaction!.attempts.push({
|
|
1291
|
+
attempt,
|
|
1292
|
+
status: "failed",
|
|
1293
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1294
|
+
source_digest: opts.capturePlanResult?.source_digest,
|
|
1295
|
+
});
|
|
1296
|
+
persistTransaction();
|
|
1297
|
+
},
|
|
1298
|
+
);
|
|
1098
1299
|
} else if (opts.reviewPack !== undefined) {
|
|
1099
1300
|
reviewPack = await captureReviewPackContext(browser, url, navResult, opts, qaCapture);
|
|
1100
1301
|
}
|
|
@@ -1153,11 +1354,7 @@ async function runBrowse(
|
|
|
1153
1354
|
"info",
|
|
1154
1355
|
);
|
|
1155
1356
|
}
|
|
1156
|
-
|
|
1157
|
-
if (opts.assert && opts.assert.length > 0) {
|
|
1158
|
-
const specs = opts.assert.map(parseAssertSpec);
|
|
1159
|
-
asserts = await browser.checkAsserts(specs);
|
|
1160
|
-
}
|
|
1357
|
+
|
|
1161
1358
|
const widthThreshold = Number.parseFloat(opts.checkWidthThreshold ?? "0.9");
|
|
1162
1359
|
const annotateWidth = widths && opts.checkWidthAnnotate !== false;
|
|
1163
1360
|
const annotateOverflow = overflow && opts.checkOverflowAnnotate !== false;
|
|
@@ -1578,6 +1775,9 @@ async function runPrintMode(
|
|
|
1578
1775
|
if (qaPlan) result.qaPlan = qaPlan;
|
|
1579
1776
|
if (qaReuse) result.qaReuse = qaReuse;
|
|
1580
1777
|
if (reviewPack) result.reviewPack = reviewPack;
|
|
1778
|
+
if (opts.capturePlanResult) result.review_pack_capture_plan = opts.capturePlanResult;
|
|
1779
|
+
if (opts.captureSourceEvidence) result.review_pack_source_evidence = opts.captureSourceEvidence;
|
|
1780
|
+
if (opts.captureTransaction) result.review_pack_transaction = opts.captureTransaction;
|
|
1581
1781
|
if (batchResult && batchResult.clipboardReads.length > 0) {
|
|
1582
1782
|
result.batchClipboardReads = batchResult.clipboardReads;
|
|
1583
1783
|
}
|
|
@@ -1626,6 +1826,7 @@ async function runTrioMode(
|
|
|
1626
1826
|
const skipScreenshot = opts.screenshot === false;
|
|
1627
1827
|
let pngPath: string | undefined;
|
|
1628
1828
|
let pngBytes: number | undefined;
|
|
1829
|
+
let pngFingerprint: CaptureFingerprint | null = null;
|
|
1629
1830
|
let captureEvalResult: unknown;
|
|
1630
1831
|
let captureViewport: { width: number; height: number } | undefined;
|
|
1631
1832
|
let captureEvidence: unknown;
|
|
@@ -1642,6 +1843,8 @@ async function runTrioMode(
|
|
|
1642
1843
|
} else {
|
|
1643
1844
|
pngBytes = await browser.screenshot(pngPath, { fullPage: opts.fullPage !== false });
|
|
1644
1845
|
}
|
|
1846
|
+
if (!opts.selector && opts.fullPage !== false)
|
|
1847
|
+
pngFingerprint = captureThumbnailFingerprint(pngPath);
|
|
1645
1848
|
written.push(pngPath);
|
|
1646
1849
|
} else if (opts.captureEvaluate) {
|
|
1647
1850
|
throw new Error("--capture-evaluate requires a screenshot; remove --no-screenshot.");
|
|
@@ -1665,6 +1868,19 @@ async function runTrioMode(
|
|
|
1665
1868
|
}
|
|
1666
1869
|
written.push(htmlPath);
|
|
1667
1870
|
|
|
1871
|
+
const thumbnailAssociation =
|
|
1872
|
+
snapshot && pngPath && pngFingerprint
|
|
1873
|
+
? registerCaptureThumbnail({
|
|
1874
|
+
htmlPath,
|
|
1875
|
+
pngPath,
|
|
1876
|
+
html: snapshot.html,
|
|
1877
|
+
preview: pngFingerprint,
|
|
1878
|
+
fullDocument: !opts.selector && opts.fullPage !== false,
|
|
1879
|
+
stylesheetsLinked: snapshot.stylesheetsLinked,
|
|
1880
|
+
resourcesLinked: snapshot.resourcesLinked,
|
|
1881
|
+
})
|
|
1882
|
+
: null;
|
|
1883
|
+
|
|
1668
1884
|
const diag = browser.diagnostics();
|
|
1669
1885
|
const jsonPath = `${prefix}.json`;
|
|
1670
1886
|
const envelope: Record<string, unknown> = {
|
|
@@ -1679,6 +1895,7 @@ async function runTrioMode(
|
|
|
1679
1895
|
...summarizeDiagnostics(diag),
|
|
1680
1896
|
};
|
|
1681
1897
|
if (pngBytes !== undefined) envelope.screenshotBytes = pngBytes;
|
|
1898
|
+
if (thumbnailAssociation) envelope.thumbnailAssociation = thumbnailAssociation;
|
|
1682
1899
|
if (snapshot) {
|
|
1683
1900
|
envelope.htmlSnapshot = {
|
|
1684
1901
|
stylesheetsInlined: snapshot.stylesheetsInlined,
|
|
@@ -1714,6 +1931,9 @@ async function runTrioMode(
|
|
|
1714
1931
|
if (qaPlan) envelope.qaPlan = qaPlan;
|
|
1715
1932
|
if (qaReuse) envelope.qaReuse = qaReuse;
|
|
1716
1933
|
if (reviewPack) envelope.reviewPack = reviewPack;
|
|
1934
|
+
if (opts.capturePlanResult) envelope.review_pack_capture_plan = opts.capturePlanResult;
|
|
1935
|
+
if (opts.captureSourceEvidence) envelope.review_pack_source_evidence = opts.captureSourceEvidence;
|
|
1936
|
+
if (opts.captureTransaction) envelope.review_pack_transaction = opts.captureTransaction;
|
|
1717
1937
|
if (batchResult && batchResult.clipboardReads.length > 0) {
|
|
1718
1938
|
envelope.batchClipboardReads = batchResult.clipboardReads;
|
|
1719
1939
|
}
|
|
@@ -2334,12 +2554,14 @@ async function expandReviewPackTile(
|
|
|
2334
2554
|
const packDir = opts.reviewPack as string;
|
|
2335
2555
|
const contextId = opts.reviewPackContext as string;
|
|
2336
2556
|
const tileId = opts.reviewPackExpand as string;
|
|
2337
|
-
const
|
|
2557
|
+
const originalContext = readPackContext(packDir, contextId);
|
|
2558
|
+
const { tile } = findPackTile([originalContext], tileId, contextId);
|
|
2559
|
+
const originalDpr = originalContext.dpr;
|
|
2338
2560
|
const region = await browser.captureRegionByScroll({
|
|
2339
|
-
x: tile.x,
|
|
2340
|
-
y: tile.scrollY,
|
|
2341
|
-
width: tile.width,
|
|
2342
|
-
height: tile.height,
|
|
2561
|
+
x: tile.x / originalDpr,
|
|
2562
|
+
y: tile.scrollY / originalDpr,
|
|
2563
|
+
width: tile.width / originalDpr,
|
|
2564
|
+
height: tile.height / originalDpr,
|
|
2343
2565
|
});
|
|
2344
2566
|
const { record, expanded } = writePackExpandedTile(packDir, contextId, {
|
|
2345
2567
|
tileId,
|
|
@@ -2368,9 +2590,9 @@ async function expandReviewPackTile(
|
|
|
2368
2590
|
* agree the tiles stand and the record says `full-page`; when any disagrees,
|
|
2369
2591
|
* every band is re-cut from its scrolled capture (same rects, same ids, only
|
|
2370
2592
|
* the pixels change) and the record says `scrolled-bands`. A probe that
|
|
2371
|
-
* cannot run, or a re-capture
|
|
2372
|
-
*
|
|
2373
|
-
*
|
|
2593
|
+
* cannot run, or a re-capture with the wrong size, leaves a missing probe;
|
|
2594
|
+
* the caller refuses to save that context. Runs while the capture browser
|
|
2595
|
+
* is open; the judge never opens one.
|
|
2374
2596
|
*/
|
|
2375
2597
|
async function reconcileCaptureFidelity(
|
|
2376
2598
|
browser: Browser,
|
|
@@ -2388,31 +2610,23 @@ async function reconcileCaptureFidelity(
|
|
|
2388
2610
|
height: tile.height / dpr,
|
|
2389
2611
|
});
|
|
2390
2612
|
const shots = new Map<number, Buffer | undefined>();
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
const
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
const captured = await browser.captureRegionByScroll(cssRect(tile));
|
|
2397
|
-
const size = pngDimensions(captured);
|
|
2613
|
+
try {
|
|
2614
|
+
const captured = await browser.captureRegionsByScroll(tiles.map(cssRect));
|
|
2615
|
+
for (const [position, shot] of captured.entries()) {
|
|
2616
|
+
const tile = tiles[position];
|
|
2617
|
+
const size = pngDimensions(shot);
|
|
2398
2618
|
if (Math.abs(size.width - tile.width) > 1 || Math.abs(size.height - tile.height) > 1) {
|
|
2399
2619
|
warnings.push(
|
|
2400
|
-
`
|
|
2401
|
-
`${size.width}×${size.height} px for a ${tile.width}×${tile.height} px band; ` +
|
|
2402
|
-
"its full-page crop was kept",
|
|
2620
|
+
`Native scrolled capture of ${tileId(position)} has different dimensions; probe incomplete.`,
|
|
2403
2621
|
);
|
|
2404
|
-
} else
|
|
2405
|
-
shot = captured;
|
|
2406
|
-
}
|
|
2407
|
-
} catch (err: unknown) {
|
|
2408
|
-
warnings.push(
|
|
2409
|
-
`review-pack: scrolled capture of ${tileId(position)} failed ` +
|
|
2410
|
-
`(${err instanceof Error ? err.message : String(err)}); its full-page crop was kept`,
|
|
2411
|
-
);
|
|
2622
|
+
} else shots.set(position, shot);
|
|
2412
2623
|
}
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2624
|
+
} catch (error) {
|
|
2625
|
+
warnings.push(
|
|
2626
|
+
`Native scrolled capture failed: ${error instanceof Error ? error.message : String(error)}; probe incomplete.`,
|
|
2627
|
+
);
|
|
2628
|
+
}
|
|
2629
|
+
const shoot = async (position: number) => shots.get(position);
|
|
2416
2630
|
// Every band is probed. A two-band sample (top and middle) missed a raster
|
|
2417
2631
|
// artifact that sat elsewhere on the page, and once every band has been
|
|
2418
2632
|
// shot by scrolling, the fallback below costs nothing extra: the same shots
|
|
@@ -2452,11 +2666,90 @@ async function reconcileCaptureFidelity(
|
|
|
2452
2666
|
|
|
2453
2667
|
/**
|
|
2454
2668
|
* Capture this render into a page review pack: one full-page screenshot,
|
|
2455
|
-
*
|
|
2669
|
+
* budgeted native tiles, one candidate set per
|
|
2456
2670
|
* --review-pack-scope selector, the serialized DOM, and the QA signature —
|
|
2457
2671
|
* all written to disk, with no vision call. The judge stage reads them back
|
|
2458
2672
|
* once the browser is gone.
|
|
2459
2673
|
*/
|
|
2674
|
+
export const REVIEW_CAPTURE_RECIPE = "native-full-page-scroll-probe/v2";
|
|
2675
|
+
|
|
2676
|
+
async function buildReviewCapturePlan(
|
|
2677
|
+
browser: Browser,
|
|
2678
|
+
opts: BrowseOpts,
|
|
2679
|
+
): Promise<PageReviewCapturePlan> {
|
|
2680
|
+
await browser.currentPage.evaluate(() => document.fonts.ready);
|
|
2681
|
+
const geometry = await browser.currentPage.evaluate(() => ({
|
|
2682
|
+
dpr: window.devicePixelRatio || 1,
|
|
2683
|
+
width: Math.max(document.documentElement.scrollWidth, document.body?.scrollWidth ?? 0),
|
|
2684
|
+
height: Math.max(document.documentElement.scrollHeight, document.body?.scrollHeight ?? 0),
|
|
2685
|
+
viewportWidth: window.innerWidth,
|
|
2686
|
+
viewportHeight: window.innerHeight,
|
|
2687
|
+
}));
|
|
2688
|
+
const { dpr } = geometry;
|
|
2689
|
+
const signature = await browser.qaSignature();
|
|
2690
|
+
// Attribute insertion order is serialization noise, not a source change.
|
|
2691
|
+
// Parse an inert document so canonicalization cannot mutate the rendered page.
|
|
2692
|
+
const canonicalDom = await browser.currentPage.evaluate((html) => {
|
|
2693
|
+
const document = new DOMParser().parseFromString(html, "text/html");
|
|
2694
|
+
for (const el of Array.from(document.querySelectorAll("*"))) {
|
|
2695
|
+
const attrs = Array.from(el.attributes)
|
|
2696
|
+
.map((a) => ({ name: a.name, value: a.value, ns: a.namespaceURI }))
|
|
2697
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
2698
|
+
for (const a of Array.from(el.attributes)) el.removeAttributeNode(a);
|
|
2699
|
+
for (const a of attrs) el.setAttributeNS(a.ns, a.name, a.value);
|
|
2700
|
+
}
|
|
2701
|
+
return document.documentElement.outerHTML;
|
|
2702
|
+
}, signature.domHtml);
|
|
2703
|
+
const source = retainPageReviewSourceIdentity(
|
|
2704
|
+
{ nodes: signature.nodes, stylesheets: signature.stylesheets, dom: canonicalDom },
|
|
2705
|
+
opts.out ?? (opts.reviewPack ? resolve(opts.reviewPack, "source") : undefined),
|
|
2706
|
+
);
|
|
2707
|
+
opts.captureSourceEvidence = source.evidence;
|
|
2708
|
+
const atoms = (await browser.visualAtoms()).map((a) => ({
|
|
2709
|
+
...a,
|
|
2710
|
+
top: Math.round(a.top * dpr),
|
|
2711
|
+
bottom: Math.round(a.bottom * dpr),
|
|
2712
|
+
}));
|
|
2713
|
+
const { band, overlap } = critiqueTilingKnobs(opts);
|
|
2714
|
+
const scopes = [];
|
|
2715
|
+
for (const selector of opts.reviewPackScope ?? []) {
|
|
2716
|
+
const rects = (await browser.elementTiles(selector)).map((r) => ({
|
|
2717
|
+
...r,
|
|
2718
|
+
x: Math.round(r.x * dpr),
|
|
2719
|
+
y: Math.round(r.y * dpr),
|
|
2720
|
+
width: Math.round(r.width * dpr),
|
|
2721
|
+
height: Math.round(r.height * dpr),
|
|
2722
|
+
}));
|
|
2723
|
+
scopes.push({ selector, rects });
|
|
2724
|
+
}
|
|
2725
|
+
const theme = opts.qaTheme === "dark" ? "dark" : "light";
|
|
2726
|
+
const viewport = opts.viewport ?? "desktop",
|
|
2727
|
+
state = opts.qaState ?? "default";
|
|
2728
|
+
return {
|
|
2729
|
+
schema: PAGE_REVIEW_CAPTURE_PLAN_SCHEMA,
|
|
2730
|
+
context_id: opts.reviewPackContext ?? `${viewport}--${theme}--${state}`,
|
|
2731
|
+
viewport,
|
|
2732
|
+
viewport_width: geometry.viewportWidth,
|
|
2733
|
+
viewport_height: geometry.viewportHeight,
|
|
2734
|
+
theme,
|
|
2735
|
+
state,
|
|
2736
|
+
dpr,
|
|
2737
|
+
page_width: Math.round(geometry.width * dpr),
|
|
2738
|
+
page_height: Math.round(geometry.height * dpr),
|
|
2739
|
+
source_digest: source.digest,
|
|
2740
|
+
recipe_version: REVIEW_CAPTURE_RECIPE,
|
|
2741
|
+
required_scopes: opts.reviewPackScope ?? [],
|
|
2742
|
+
candidates: reviewCandidateRects({
|
|
2743
|
+
width: Math.round(geometry.width * dpr),
|
|
2744
|
+
height: Math.round(geometry.height * dpr),
|
|
2745
|
+
bandHeight: band,
|
|
2746
|
+
overlap,
|
|
2747
|
+
atoms,
|
|
2748
|
+
scopes,
|
|
2749
|
+
}),
|
|
2750
|
+
};
|
|
2751
|
+
}
|
|
2752
|
+
|
|
2460
2753
|
async function captureReviewPackContext(
|
|
2461
2754
|
browser: Browser,
|
|
2462
2755
|
targetArg: string,
|
|
@@ -2465,120 +2758,125 @@ async function captureReviewPackContext(
|
|
|
2465
2758
|
qaCapture: QaCaptureState | undefined,
|
|
2466
2759
|
): Promise<ReviewPackReport> {
|
|
2467
2760
|
const packDir = opts.reviewPack as string;
|
|
2468
|
-
const
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
const hitRects = parseReviewPackHitRects(opts.reviewPackHitRect);
|
|
2482
|
-
const banded = withBands
|
|
2483
|
-
? tilesFromFullPage(buffer, { ...tiling, hitRects, maxHitBands: REVIEW_PACK_HIT_RECT_MAX })
|
|
2484
|
-
: undefined;
|
|
2485
|
-
if (banded?.coverage.capped) {
|
|
2486
|
-
emit.log(
|
|
2487
|
-
`review-pack: page banded to the ${maxTiles}-tile cap (raise --check-critique-max-tiles for full coverage)`,
|
|
2488
|
-
"warn",
|
|
2489
|
-
);
|
|
2490
|
-
}
|
|
2491
|
-
if (banded && banded.hitBands > 0) {
|
|
2492
|
-
emit.log(
|
|
2493
|
-
`review-pack: ${banded.hitBands} band(s) below the tile cap captured for ${hitRects.length} gate-hit rect(s)`,
|
|
2494
|
-
"info",
|
|
2495
|
-
);
|
|
2496
|
-
}
|
|
2497
|
-
// Fidelity: prove the full-page screenshot against scrolled viewport
|
|
2498
|
-
// renders while the browser is still open; on disagreement the bands are
|
|
2499
|
-
// re-cut from scrolled captures (see reconcileCaptureFidelity).
|
|
2500
|
-
let bandTiles = banded?.tiles ?? [];
|
|
2501
|
-
let captureFidelity: PageReviewCaptureFidelity | undefined;
|
|
2502
|
-
const fidelityWarnings: string[] = [];
|
|
2503
|
-
if (bandTiles.length > 0) {
|
|
2504
|
-
const reconciled = await reconcileCaptureFidelity(browser, bandTiles);
|
|
2505
|
-
bandTiles = reconciled.tiles;
|
|
2506
|
-
captureFidelity = reconciled.fidelity;
|
|
2507
|
-
fidelityWarnings.push(...reconciled.warnings);
|
|
2508
|
-
for (const warning of reconciled.warnings) emit.log(warning, "warn");
|
|
2509
|
-
emit.log(
|
|
2510
|
-
`review-pack: capture fidelity ${captureFidelity.source} ` +
|
|
2511
|
-
`(${captureFidelity.probed.length} band(s) probed, ${captureFidelity.mismatched.length} mismatched)`,
|
|
2512
|
-
"info",
|
|
2513
|
-
);
|
|
2514
|
-
}
|
|
2515
|
-
const scopeTiles: Array<{ selector: string; tiles: CritiqueTile[]; coverage: CritiqueCoverage }> =
|
|
2516
|
-
[];
|
|
2517
|
-
for (const selector of opts.reviewPackScope ?? []) {
|
|
2518
|
-
const elementRects = await browser.elementTiles(selector);
|
|
2519
|
-
const scoped = tilesFromFullPage(buffer, { ...tiling, elementRects });
|
|
2520
|
-
if (elementRects.length > maxTiles) {
|
|
2521
|
-
emit.log(
|
|
2522
|
-
`review-pack: ${elementRects.length} elements matched ${selector}, capped to ${maxTiles} tiles`,
|
|
2523
|
-
"warn",
|
|
2761
|
+
const plan = await buildReviewCapturePlan(browser, opts);
|
|
2762
|
+
let allocation: PageReviewContextAllocation;
|
|
2763
|
+
if (opts.captureTransactionAllocation) {
|
|
2764
|
+
try {
|
|
2765
|
+
allocation = validatePageReviewAllocation(opts.captureTransactionAllocation, plan);
|
|
2766
|
+
} catch (error) {
|
|
2767
|
+
throw new CaptureSourceChanged(String(error));
|
|
2768
|
+
}
|
|
2769
|
+
} else if (opts.reviewPackAllocation) {
|
|
2770
|
+
try {
|
|
2771
|
+
allocation = validatePageReviewAllocation(
|
|
2772
|
+
JSON.parse(readFileSync(resolve(opts.reviewPackAllocation), "utf8")),
|
|
2773
|
+
plan,
|
|
2524
2774
|
);
|
|
2775
|
+
} catch (error) {
|
|
2776
|
+
if (error instanceof Error && opts.captureSourceEvidence)
|
|
2777
|
+
error.message += ` Current source identity: ${opts.captureSourceEvidence.path} (sha256 ${plan.source_digest}).`;
|
|
2778
|
+
throw error;
|
|
2525
2779
|
}
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
),
|
|
2540
|
-
bands_total: coverage.bands_total + scope.coverage.bands_total,
|
|
2541
|
-
bands_reviewed: coverage.bands_reviewed + scope.coverage.bands_reviewed,
|
|
2542
|
-
capped: coverage.capped || scope.coverage.capped,
|
|
2543
|
-
}
|
|
2544
|
-
: scope.coverage;
|
|
2780
|
+
} else {
|
|
2781
|
+
// Standalone capture retains its 24-tile default; it still obeys one hard total.
|
|
2782
|
+
const maxTiles = critiqueTilingKnobs(opts).maxTiles;
|
|
2783
|
+
const hits = parseReviewPackHitRects(opts.reviewPackHitRect);
|
|
2784
|
+
for (const c of plan.candidates) {
|
|
2785
|
+
c.gate_hits = hits.flatMap((h, i) =>
|
|
2786
|
+
c.rect.x < (h.x + h.width) * plan.dpr &&
|
|
2787
|
+
c.rect.x + c.rect.width > h.x * plan.dpr &&
|
|
2788
|
+
c.rect.y < (h.y + h.height) * plan.dpr &&
|
|
2789
|
+
c.rect.y + c.rect.height > h.y * plan.dpr
|
|
2790
|
+
? [{ check_id: `hit:${i + 1}`, severity: "high" as const }]
|
|
2791
|
+
: [],
|
|
2792
|
+
);
|
|
2545
2793
|
}
|
|
2794
|
+
allocation = allocateTileBudget([plan], maxTiles).contexts[0];
|
|
2546
2795
|
}
|
|
2547
|
-
const
|
|
2548
|
-
|
|
2549
|
-
|
|
2796
|
+
const start = performance.now();
|
|
2797
|
+
const buffer = await browser.fullPageScreenshotBuffer();
|
|
2798
|
+
const size = pngDimensions(buffer);
|
|
2799
|
+
if (size.width !== plan.page_width || size.height !== plan.page_height)
|
|
2800
|
+
throw opts.captureTransactionAllocation
|
|
2801
|
+
? new CaptureSourceChanged("Page review image geometry changed after planning.")
|
|
2802
|
+
: new Error("Page review image geometry changed after planning; rerun the review.");
|
|
2803
|
+
const decoded = PNG.sync.read(buffer);
|
|
2804
|
+
const fullPageMs = performance.now() - start;
|
|
2805
|
+
const byId = new Map(plan.candidates.map((c) => [c.id, c]));
|
|
2806
|
+
const selected = allocation.selected_ids.map((id) => byId.get(id)!);
|
|
2807
|
+
const cropStart = performance.now();
|
|
2808
|
+
const tiles: CritiqueTile[] = selected.map((c, index) => ({
|
|
2809
|
+
index,
|
|
2810
|
+
label: c.label,
|
|
2811
|
+
x: c.rect.x,
|
|
2812
|
+
scrollY: c.rect.y,
|
|
2813
|
+
width: c.rect.width,
|
|
2814
|
+
height: c.rect.height,
|
|
2815
|
+
pngBase64: cropNativePng(decoded, c.rect).toString("base64"),
|
|
2550
2816
|
}));
|
|
2817
|
+
const cropMs = performance.now() - cropStart;
|
|
2818
|
+
const probeStart = performance.now();
|
|
2819
|
+
const reconciled = await reconcileCaptureFidelity(browser, tiles);
|
|
2820
|
+
const probeMs = performance.now() - probeStart;
|
|
2821
|
+
// A lazy image can change the document bottom while a native probe is running.
|
|
2822
|
+
// Revalidate even failed probes so a changed plan gets the same bounded retry.
|
|
2823
|
+
if (opts.captureTransactionAllocation) {
|
|
2824
|
+
const after = await buildReviewCapturePlan(browser, opts);
|
|
2825
|
+
try {
|
|
2826
|
+
validatePageReviewAllocation(allocation, after);
|
|
2827
|
+
} catch (error) {
|
|
2828
|
+
throw new CaptureSourceChanged(String(error));
|
|
2829
|
+
}
|
|
2830
|
+
}
|
|
2831
|
+
if (reconciled.fidelity.probed.length !== tiles.length)
|
|
2832
|
+
throw new Error(
|
|
2833
|
+
"Page review native fidelity probe incomplete; no context was saved. " +
|
|
2834
|
+
reconciled.warnings.join("; "),
|
|
2835
|
+
);
|
|
2836
|
+
const bandTiles: CritiqueTile[] = [];
|
|
2837
|
+
const scopes = new Map<string, CritiqueTile[]>();
|
|
2838
|
+
for (const [index, c] of selected.entries()) {
|
|
2839
|
+
const tile = reconciled.tiles[index];
|
|
2840
|
+
if (!c.scope) bandTiles.push(tile);
|
|
2841
|
+
else {
|
|
2842
|
+
const group = scopes.get(c.scope) ?? [];
|
|
2843
|
+
group.push(tile);
|
|
2844
|
+
scopes.set(c.scope, group);
|
|
2845
|
+
}
|
|
2846
|
+
}
|
|
2847
|
+
|
|
2551
2848
|
const capture = qaCapture ?? (await captureQaState(browser, targetArg, navResult.url, opts));
|
|
2552
|
-
const
|
|
2553
|
-
|
|
2554
|
-
...capture.context,
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2849
|
+
const writeContext = (destination: string) =>
|
|
2850
|
+
writePackContext(destination, {
|
|
2851
|
+
context: { ...capture.context, id: plan.context_id },
|
|
2852
|
+
url: navResult.url,
|
|
2853
|
+
title: navResult.title,
|
|
2854
|
+
fullPage: buffer,
|
|
2855
|
+
pageWidth: plan.page_width / plan.dpr,
|
|
2856
|
+
pageHeight: plan.page_height / plan.dpr,
|
|
2857
|
+
tiles: bandTiles,
|
|
2858
|
+
coverage: allocation.coverage,
|
|
2859
|
+
scopeTiles: [...scopes].map(([selector, tiles]) => ({ selector, tiles })),
|
|
2860
|
+
signature: capture.signature,
|
|
2861
|
+
domHtml: capture.domHtml,
|
|
2862
|
+
captureFidelity: reconciled.fidelity,
|
|
2863
|
+
viewportSize: { width: plan.viewport_width, height: plan.viewport_height },
|
|
2864
|
+
dpr: plan.dpr,
|
|
2865
|
+
recipeVersion: plan.recipe_version,
|
|
2866
|
+
allocationCoverage: allocation.coverage,
|
|
2867
|
+
capturePlan: allocation.plan,
|
|
2868
|
+
hitBands: 0,
|
|
2869
|
+
});
|
|
2870
|
+
const record = opts.captureTransactionAllocation
|
|
2871
|
+
? writeCaptureTransaction(packDir, plan.context_id, writeContext)
|
|
2872
|
+
: writeContext(packDir);
|
|
2873
|
+
const warnings = [...reconciled.warnings];
|
|
2874
|
+
if (allocation.coverage.capped)
|
|
2875
|
+
warnings.push(
|
|
2876
|
+
`Review coverage is incomplete: ${allocation.coverage.uncovered_intervals.length} uncovered intervals and ${allocation.coverage.omitted_scopes.length} omitted scopes.`,
|
|
2877
|
+
);
|
|
2576
2878
|
emit.log(
|
|
2577
|
-
`review-pack: ${record.id}
|
|
2578
|
-
(record.scopes.length > 0
|
|
2579
|
-
? ` (${record.scopes.map((s) => `${s.selector}: ${s.tiles}`).join(", ")})`
|
|
2580
|
-
: "") +
|
|
2581
|
-
` written to ${packDir}`,
|
|
2879
|
+
`review-pack: ${record.id} captured ${record.tiles.length} tiles; full-page ${Math.round(fullPageMs)} ms, crop ${Math.round(cropMs)} ms, native probe ${Math.round(probeMs)} ms`,
|
|
2582
2880
|
"info",
|
|
2583
2881
|
);
|
|
2584
2882
|
return {
|
|
@@ -2588,9 +2886,8 @@ async function captureReviewPackContext(
|
|
|
2588
2886
|
scopes: record.scopes,
|
|
2589
2887
|
coverage: record.coverage,
|
|
2590
2888
|
files: record.files,
|
|
2591
|
-
|
|
2592
|
-
...(
|
|
2593
|
-
...(fidelityWarnings.length > 0 ? { warnings: fidelityWarnings } : {}),
|
|
2889
|
+
capture_fidelity: reconciled.fidelity,
|
|
2890
|
+
...(warnings.length ? { warnings } : {}),
|
|
2594
2891
|
};
|
|
2595
2892
|
}
|
|
2596
2893
|
|
|
@@ -2735,7 +3032,7 @@ async function waitForTerminalEnter(signal: AbortSignal): Promise<void> {
|
|
|
2735
3032
|
});
|
|
2736
3033
|
}
|
|
2737
3034
|
|
|
2738
|
-
/** Upper bound on --review-pack-hit-rect values
|
|
3035
|
+
/** Upper bound on manually supplied --review-pack-hit-rect values. */
|
|
2739
3036
|
const REVIEW_PACK_HIT_RECT_MAX = 50;
|
|
2740
3037
|
|
|
2741
3038
|
/** `x,y,w,h` integer rectangles from repeated --review-pack-hit-rect flags. */
|