harnery 0.36.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.
- package/dist/commander.d.ts +9 -0
- package/dist/commander.d.ts.map +1 -1
- package/dist/commander.js +10 -1
- package/dist/commands/admission.d.ts +21 -0
- package/dist/commands/admission.d.ts.map +1 -0
- package/dist/commands/admission.js +565 -0
- package/dist/commands/agents.d.ts +7 -0
- package/dist/commands/agents.d.ts.map +1 -1
- package/dist/commands/agents.js +148 -47
- package/dist/commands/artifacts.d.ts.map +1 -1
- package/dist/commands/artifacts.js +73 -2
- package/dist/commands/browse-ai.d.ts +2 -2
- package/dist/commands/browse-ai.d.ts.map +1 -1
- package/dist/commands/browse-ai.js +6 -4
- package/dist/commands/browse.d.ts.map +1 -1
- package/dist/commands/browse.js +360 -21
- package/dist/commands/diagnostics.d.ts +4 -0
- package/dist/commands/diagnostics.d.ts.map +1 -0
- package/dist/commands/diagnostics.js +229 -0
- package/dist/commands/fetch.js +1 -0
- package/dist/commands/files.d.ts +12 -0
- package/dist/commands/files.d.ts.map +1 -1
- package/dist/commands/files.js +54 -1
- package/dist/commands/governor.d.ts.map +1 -1
- package/dist/commands/governor.js +4 -0
- package/dist/commands/ledger-v3.d.ts.map +1 -1
- package/dist/commands/ledger-v3.js +71 -2
- package/dist/commands/qa-record.d.ts +144 -0
- package/dist/commands/qa-record.d.ts.map +1 -0
- package/dist/commands/qa-record.js +0 -0
- package/dist/commands/qa-run.d.ts +8 -0
- package/dist/commands/qa-run.d.ts.map +1 -0
- package/dist/commands/qa-run.js +396 -0
- package/dist/commands/qa-status.d.ts +71 -0
- package/dist/commands/qa-status.d.ts.map +1 -0
- package/dist/commands/qa-status.js +490 -0
- package/dist/commands/qa-verify.d.ts +40 -0
- package/dist/commands/qa-verify.d.ts.map +1 -0
- package/dist/commands/qa-verify.js +180 -0
- package/dist/commands/resources.d.ts +4 -0
- package/dist/commands/resources.d.ts.map +1 -0
- package/dist/commands/resources.js +29 -0
- package/dist/commands/review-pack.d.ts +4 -0
- package/dist/commands/review-pack.d.ts.map +1 -0
- package/dist/commands/review-pack.js +1001 -0
- package/dist/commands/supervisor.d.ts +4 -0
- package/dist/commands/supervisor.d.ts.map +1 -0
- package/dist/commands/supervisor.js +101 -0
- package/dist/commands/web.d.ts.map +1 -1
- package/dist/commands/web.js +17 -0
- package/dist/commands/workflow.d.ts.map +1 -1
- package/dist/commands/workflow.js +4 -0
- package/dist/core/agents/cli.js +7 -3
- package/dist/core/agents/health.d.ts +17 -0
- package/dist/core/agents/health.d.ts.map +1 -0
- package/dist/core/agents/health.js +109 -0
- package/dist/core/agents/mailbox.d.ts +92 -0
- package/dist/core/agents/mailbox.d.ts.map +1 -0
- package/dist/core/agents/mailbox.js +325 -0
- package/dist/core/agents/qa-signal.d.ts +111 -0
- package/dist/core/agents/qa-signal.d.ts.map +1 -0
- package/dist/core/agents/qa-signal.js +231 -0
- package/dist/core/agents/reconcile-coordination-v3.d.ts +44 -0
- package/dist/core/agents/reconcile-coordination-v3.d.ts.map +1 -0
- package/dist/core/agents/reconcile-coordination-v3.js +58 -0
- package/dist/core/agents/render/prompt-context.d.ts +5 -0
- package/dist/core/agents/render/prompt-context.d.ts.map +1 -1
- package/dist/core/agents/render/prompt-context.js +34 -13
- package/dist/core/agents/render/session-context.d.ts +8 -0
- package/dist/core/agents/render/session-context.d.ts.map +1 -1
- package/dist/core/agents/render/session-context.js +34 -3
- package/dist/core/agents/rules/commit-conflict.d.ts.map +1 -1
- package/dist/core/agents/rules/commit-conflict.js +50 -1
- package/dist/core/agents/rules/stop-hook.d.ts.map +1 -1
- package/dist/core/agents/rules/stop-hook.js +6 -10
- package/dist/core/agents/session-finalizer-v3.js +4 -1
- package/dist/core/agents/session-name-display.d.ts +20 -5
- package/dist/core/agents/session-name-display.d.ts.map +1 -1
- package/dist/core/agents/session-name-display.js +67 -7
- package/dist/core/agents/state/heartbeat-reader.d.ts +7 -0
- package/dist/core/agents/state/heartbeat-reader.d.ts.map +1 -1
- package/dist/core/agents/state/heartbeat-writer.d.ts +11 -0
- package/dist/core/agents/state/heartbeat-writer.d.ts.map +1 -1
- package/dist/core/agents/state/heartbeat-writer.js +17 -0
- package/dist/core/agents/state/live-coordination-view.d.ts.map +1 -1
- package/dist/core/agents/state/live-coordination-view.js +1 -0
- package/dist/core/agents/state/live-coordination-writer.js +5 -0
- package/dist/core/artifacts/constants.d.ts +1 -1
- package/dist/core/artifacts/constants.js +1 -1
- package/dist/core/artifacts/index.d.ts +82 -6
- package/dist/core/artifacts/index.d.ts.map +1 -1
- package/dist/core/artifacts/index.js +410 -15
- package/dist/core/config.d.ts +20 -0
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +59 -0
- package/dist/core/diagnostics/advice.d.ts +9 -0
- package/dist/core/diagnostics/advice.d.ts.map +1 -0
- package/dist/core/diagnostics/advice.js +112 -0
- package/dist/core/diagnostics/bundle.d.ts +51 -0
- package/dist/core/diagnostics/bundle.d.ts.map +1 -0
- package/dist/core/diagnostics/bundle.js +968 -0
- package/dist/core/diagnostics/comparison.d.ts +5 -0
- package/dist/core/diagnostics/comparison.d.ts.map +1 -0
- package/dist/core/diagnostics/comparison.js +444 -0
- package/dist/core/diagnostics/contract.d.ts +245 -0
- package/dist/core/diagnostics/contract.d.ts.map +1 -0
- package/dist/core/diagnostics/contract.js +18 -0
- package/dist/core/diagnostics/identity.d.ts +3 -0
- package/dist/core/diagnostics/identity.d.ts.map +1 -0
- package/dist/core/diagnostics/identity.js +10 -0
- package/dist/core/diagnostics/index.d.ts +8 -0
- package/dist/core/diagnostics/index.d.ts.map +1 -0
- package/dist/core/diagnostics/index.js +7 -0
- package/dist/core/diagnostics/replay.d.ts +8 -0
- package/dist/core/diagnostics/replay.d.ts.map +1 -0
- package/dist/core/diagnostics/replay.js +213 -0
- package/dist/core/diagnostics/sanitize.d.ts +9 -0
- package/dist/core/diagnostics/sanitize.d.ts.map +1 -0
- package/dist/core/diagnostics/sanitize.js +84 -0
- package/dist/core/events/legacy-storage/compression.d.ts +13 -0
- package/dist/core/events/legacy-storage/compression.d.ts.map +1 -0
- package/dist/core/events/legacy-storage/compression.js +107 -0
- package/dist/core/events/legacy-storage/index.d.ts +1 -0
- package/dist/core/events/legacy-storage/index.d.ts.map +1 -1
- package/dist/core/events/legacy-storage/index.js +1 -0
- package/dist/core/events/v3/archive-retention.d.ts +33 -0
- package/dist/core/events/v3/archive-retention.d.ts.map +1 -0
- package/dist/core/events/v3/archive-retention.js +195 -0
- package/dist/core/events/v3/bootstrap.d.ts.map +1 -1
- package/dist/core/events/v3/bootstrap.js +10 -0
- package/dist/core/events/v3/capabilities.d.ts +1 -1
- package/dist/core/events/v3/capabilities.d.ts.map +1 -1
- package/dist/core/events/v3/capabilities.js +1 -0
- package/dist/core/events/v3/coordination-view.d.ts +3 -0
- package/dist/core/events/v3/coordination-view.d.ts.map +1 -1
- package/dist/core/events/v3/coordination-view.js +69 -8
- package/dist/core/events/v3/index.d.ts +1 -0
- package/dist/core/events/v3/index.d.ts.map +1 -1
- package/dist/core/events/v3/index.js +1 -0
- package/dist/core/events/v3/live-routing.d.ts.map +1 -1
- package/dist/core/events/v3/live-routing.js +1 -0
- package/dist/core/events/v3/producers/hook-base.d.ts +1 -1
- package/dist/core/events/v3/producers/hook-base.d.ts.map +1 -1
- package/dist/core/events/v3/producers/hook-base.js +13 -2
- package/dist/core/events/v3/producers/intake.d.ts.map +1 -1
- package/dist/core/events/v3/producers/intake.js +10 -2
- package/dist/core/events/v3/producers/recorder.d.ts +39 -0
- package/dist/core/events/v3/producers/recorder.d.ts.map +1 -1
- package/dist/core/events/v3/producers/recorder.js +350 -17
- package/dist/core/hooks/adapter/output.d.ts +8 -0
- package/dist/core/hooks/adapter/output.d.ts.map +1 -1
- package/dist/core/hooks/adapter/output.js +11 -1
- package/dist/core/hooks/cli.js +303 -219
- package/dist/core/hooks/health.d.ts +59 -0
- package/dist/core/hooks/health.d.ts.map +1 -0
- package/dist/core/hooks/health.js +84 -0
- package/dist/core/hooks/resolve/transcript.d.ts.map +1 -1
- package/dist/core/hooks/resolve/transcript.js +10 -3
- package/dist/core/hooks/session-name-presence.d.ts.map +1 -1
- package/dist/core/hooks/session-name-presence.js +4 -1
- package/dist/core/qa-artifacts.d.ts +20 -0
- package/dist/core/qa-artifacts.d.ts.map +1 -0
- package/dist/core/qa-artifacts.js +110 -0
- package/dist/core/resources/contract.d.ts +96 -0
- package/dist/core/resources/contract.d.ts.map +1 -0
- package/dist/core/resources/contract.js +2 -0
- package/dist/core/resources/index.d.ts +6 -0
- package/dist/core/resources/index.d.ts.map +1 -0
- package/dist/core/resources/index.js +5 -0
- package/dist/core/resources/sampler.d.ts +34 -0
- package/dist/core/resources/sampler.d.ts.map +1 -0
- package/dist/core/resources/sampler.js +458 -0
- package/dist/core/resources/service-status.d.ts +3 -0
- package/dist/core/resources/service-status.d.ts.map +1 -0
- package/dist/core/resources/service-status.js +48 -0
- package/dist/core/resources/service.d.ts +26 -0
- package/dist/core/resources/service.d.ts.map +1 -0
- package/dist/core/resources/service.js +292 -0
- package/dist/core/resources/storage.d.ts +13 -0
- package/dist/core/resources/storage.d.ts.map +1 -0
- package/dist/core/resources/storage.js +30 -0
- package/dist/core/storage/atomic-json.d.ts +3 -0
- package/dist/core/storage/atomic-json.d.ts.map +1 -0
- package/dist/core/storage/atomic-json.js +15 -0
- package/dist/core/storage/builtins.d.ts.map +1 -1
- package/dist/core/storage/builtins.js +45 -6
- package/dist/core/storage/logger.d.ts +2 -0
- package/dist/core/storage/logger.d.ts.map +1 -1
- package/dist/core/storage/logger.js +2 -0
- package/dist/core/storage/query.d.ts +14 -0
- package/dist/core/storage/query.d.ts.map +1 -1
- package/dist/core/storage/query.js +51 -0
- package/dist/core/supervisor/activity.d.ts +4 -0
- package/dist/core/supervisor/activity.d.ts.map +1 -0
- package/dist/core/supervisor/activity.js +141 -0
- package/dist/core/supervisor/contract.d.ts +268 -0
- package/dist/core/supervisor/contract.d.ts.map +1 -0
- package/dist/core/supervisor/contract.js +35 -0
- package/dist/core/supervisor/explanations.d.ts +4 -0
- package/dist/core/supervisor/explanations.d.ts.map +1 -0
- package/dist/core/supervisor/explanations.js +71 -0
- package/dist/core/supervisor/findings.d.ts +20 -0
- package/dist/core/supervisor/findings.d.ts.map +1 -0
- package/dist/core/supervisor/findings.js +399 -0
- package/dist/core/supervisor/history.d.ts +9 -0
- package/dist/core/supervisor/history.d.ts.map +1 -0
- package/dist/core/supervisor/history.js +46 -0
- package/dist/core/supervisor/hook-health-alerts.d.ts +14 -0
- package/dist/core/supervisor/hook-health-alerts.d.ts.map +1 -0
- package/dist/core/supervisor/hook-health-alerts.js +70 -0
- package/dist/core/supervisor/hook-health-storage.d.ts +5 -0
- package/dist/core/supervisor/hook-health-storage.d.ts.map +1 -0
- package/dist/core/supervisor/hook-health-storage.js +24 -0
- package/dist/core/supervisor/hook-health.d.ts +72 -0
- package/dist/core/supervisor/hook-health.d.ts.map +1 -0
- package/dist/core/supervisor/hook-health.js +203 -0
- package/dist/core/supervisor/hooks.d.ts +10 -0
- package/dist/core/supervisor/hooks.d.ts.map +1 -0
- package/dist/core/supervisor/hooks.js +28 -0
- package/dist/core/supervisor/index.d.ts +15 -0
- package/dist/core/supervisor/index.d.ts.map +1 -0
- package/dist/core/supervisor/index.js +14 -0
- package/dist/core/supervisor/log-feed.d.ts +9 -0
- package/dist/core/supervisor/log-feed.d.ts.map +1 -0
- package/dist/core/supervisor/log-feed.js +103 -0
- package/dist/core/supervisor/service.d.ts +29 -0
- package/dist/core/supervisor/service.d.ts.map +1 -0
- package/dist/core/supervisor/service.js +450 -0
- package/dist/core/supervisor/services.d.ts +11 -0
- package/dist/core/supervisor/services.d.ts.map +1 -0
- package/dist/core/supervisor/services.js +155 -0
- package/dist/core/supervisor/status.d.ts +3 -0
- package/dist/core/supervisor/status.d.ts.map +1 -0
- package/dist/core/supervisor/status.js +48 -0
- package/dist/core/supervisor/storage.d.ts +30 -0
- package/dist/core/supervisor/storage.d.ts.map +1 -0
- package/dist/core/supervisor/storage.js +73 -0
- package/dist/core/supervisor/timeline.d.ts +3 -0
- package/dist/core/supervisor/timeline.d.ts.map +1 -0
- package/dist/core/supervisor/timeline.js +87 -0
- package/dist/core/workflow/admission.d.ts +23 -0
- package/dist/core/workflow/admission.d.ts.map +1 -0
- package/dist/core/workflow/admission.js +136 -0
- package/dist/core/workflow/engine.d.ts.map +1 -1
- package/dist/core/workflow/engine.js +77 -2
- package/dist/core/workflow/index.d.ts +3 -2
- package/dist/core/workflow/index.d.ts.map +1 -1
- package/dist/core/workflow/index.js +2 -1
- package/dist/core/workflow/proof.d.ts +2 -1
- package/dist/core/workflow/proof.d.ts.map +1 -1
- package/dist/core/workflow/proof.js +39 -1
- package/dist/core/workflow/run-state.d.ts +3 -2
- package/dist/core/workflow/run-state.d.ts.map +1 -1
- package/dist/core/workflow/run-state.js +8 -1
- package/dist/core/workflow/types.d.ts +33 -1
- package/dist/core/workflow/types.d.ts.map +1 -1
- package/dist/core/workflow/types.js +2 -1
- package/dist/lib/admission.d.ts +71 -0
- package/dist/lib/admission.d.ts.map +1 -0
- package/dist/lib/admission.js +264 -0
- package/dist/lib/agent-browser/client.d.ts +1 -1
- package/dist/lib/agent-browser/client.d.ts.map +1 -1
- package/dist/lib/agent-browser/client.js +1 -5
- package/dist/lib/browser/capture-fidelity.d.ts +39 -0
- package/dist/lib/browser/capture-fidelity.d.ts.map +1 -0
- package/dist/lib/browser/capture-fidelity.js +84 -0
- package/dist/lib/browser/client.d.ts +46 -1
- package/dist/lib/browser/client.d.ts.map +1 -1
- package/dist/lib/browser/client.js +168 -9
- package/dist/lib/browser/critique.d.ts +38 -1
- package/dist/lib/browser/critique.d.ts.map +1 -1
- package/dist/lib/browser/critique.js +34 -6
- package/dist/lib/browser/index.d.ts +5 -1
- package/dist/lib/browser/index.d.ts.map +1 -1
- package/dist/lib/browser/index.js +4 -0
- package/dist/lib/browser/page-review-judge.d.ts +64 -0
- package/dist/lib/browser/page-review-judge.d.ts.map +1 -0
- package/dist/lib/browser/page-review-judge.js +270 -0
- package/dist/lib/browser/page-review-pack.d.ts +613 -0
- package/dist/lib/browser/page-review-pack.d.ts.map +1 -0
- package/dist/lib/browser/page-review-pack.js +1751 -0
- package/dist/lib/browser/qa-run-contracts.d.ts +377 -0
- package/dist/lib/browser/qa-run-contracts.d.ts.map +1 -0
- package/dist/lib/browser/qa-run-contracts.js +361 -0
- package/dist/lib/browser/qa-run.d.ts +141 -0
- package/dist/lib/browser/qa-run.d.ts.map +1 -0
- package/dist/lib/browser/qa-run.js +1283 -0
- package/dist/lib/browser/request-diagnostics.d.ts +13 -0
- package/dist/lib/browser/request-diagnostics.d.ts.map +1 -0
- package/dist/lib/browser/request-diagnostics.js +18 -0
- package/dist/lib/browser/tiling.d.ts +19 -0
- package/dist/lib/browser/tiling.d.ts.map +1 -1
- package/dist/lib/browser/tiling.js +28 -0
- package/dist/lib/cookies/client.d.ts +9 -0
- package/dist/lib/cookies/client.d.ts.map +1 -1
- package/dist/lib/cookies/client.js +197 -44
- package/dist/lib/cookies/extra.d.ts +18 -0
- package/dist/lib/cookies/extra.d.ts.map +1 -0
- package/dist/lib/cookies/extra.js +14 -0
- package/dist/lib/cookies/index.d.ts +2 -1
- package/dist/lib/cookies/index.d.ts.map +1 -1
- package/dist/lib/cookies/index.js +2 -1
- package/dist/lib/coord-root-id.d.ts +9 -0
- package/dist/lib/coord-root-id.d.ts.map +1 -0
- package/dist/lib/coord-root-id.js +17 -0
- package/dist/lib/durable-job.d.ts +124 -0
- package/dist/lib/durable-job.d.ts.map +1 -0
- package/dist/lib/durable-job.js +296 -0
- package/dist/lib/http/client.d.ts +7 -1
- package/dist/lib/http/client.d.ts.map +1 -1
- package/dist/lib/http/client.js +2 -0
- package/dist/lib/instructions/apply.d.ts +3 -1
- package/dist/lib/instructions/apply.d.ts.map +1 -1
- package/dist/lib/instructions/apply.js +74 -3
- package/dist/lib/instructions/templates.d.ts.map +1 -1
- package/dist/lib/instructions/templates.js +11 -2
- package/package.json +19 -2
- package/src/commander.ts +75 -1
- package/src/commands/admission.ts +699 -0
- package/src/commands/agents.ts +189 -50
- package/src/commands/artifacts.ts +132 -18
- package/src/commands/browse-ai.ts +10 -5
- package/src/commands/browse.ts +496 -20
- package/src/commands/diagnostics.ts +271 -0
- package/src/commands/fetch.ts +1 -0
- package/src/commands/files.ts +102 -2
- package/src/commands/governor.ts +8 -0
- package/src/commands/ledger-v3.ts +82 -0
- package/src/commands/qa-record.ts +682 -0
- package/src/commands/qa-run.ts +503 -0
- package/src/commands/qa-status.ts +608 -0
- package/src/commands/qa-verify.ts +238 -0
- package/src/commands/resources.ts +38 -0
- package/src/commands/review-pack.ts +1281 -0
- package/src/commands/supervisor.ts +141 -0
- package/src/commands/web.ts +19 -0
- package/src/commands/workflow.ts +8 -0
- package/src/core/agents/cli.ts +7 -3
- package/src/core/agents/health.ts +142 -0
- package/src/core/agents/mailbox.ts +373 -0
- package/src/core/agents/qa-signal.ts +261 -0
- package/src/core/agents/reconcile-coordination-v3.ts +77 -0
- package/src/core/agents/render/prompt-context.ts +42 -12
- package/src/core/agents/render/session-context.ts +37 -3
- package/src/core/agents/rules/commit-conflict.ts +58 -1
- package/src/core/agents/rules/stop-hook.ts +6 -12
- package/src/core/agents/session-finalizer-v3.ts +4 -1
- package/src/core/agents/session-name-display.ts +78 -7
- package/src/core/agents/state/heartbeat-reader.ts +7 -0
- package/src/core/agents/state/heartbeat-writer.ts +23 -0
- package/src/core/agents/state/live-coordination-view.ts +1 -0
- package/src/core/agents/state/live-coordination-writer.ts +5 -0
- package/src/core/artifacts/constants.ts +1 -1
- package/src/core/artifacts/index.ts +565 -30
- package/src/core/config.ts +134 -2
- package/src/core/diagnostics/advice.ts +147 -0
- package/src/core/diagnostics/bundle.ts +1250 -0
- package/src/core/diagnostics/comparison.ts +565 -0
- package/src/core/diagnostics/contract.ts +311 -0
- package/src/core/diagnostics/identity.ts +11 -0
- package/src/core/diagnostics/index.ts +7 -0
- package/src/core/diagnostics/replay.ts +314 -0
- package/src/core/diagnostics/sanitize.ts +97 -0
- package/src/core/events/legacy-storage/compression.ts +130 -0
- package/src/core/events/legacy-storage/index.ts +1 -0
- package/src/core/events/v3/archive-retention.ts +251 -0
- package/src/core/events/v3/bootstrap.ts +10 -0
- package/src/core/events/v3/capabilities.ts +2 -0
- package/src/core/events/v3/coordination-view.ts +100 -11
- package/src/core/events/v3/index.ts +9 -0
- package/src/core/events/v3/live-routing.ts +1 -0
- package/src/core/events/v3/producers/hook-base.ts +14 -2
- package/src/core/events/v3/producers/intake.ts +10 -2
- package/src/core/events/v3/producers/recorder.ts +420 -17
- package/src/core/hooks/adapter/output.ts +12 -1
- package/src/core/hooks/cli.ts +332 -247
- package/src/core/hooks/health.ts +146 -0
- package/src/core/hooks/resolve/transcript.ts +10 -3
- package/src/core/hooks/session-name-presence.ts +6 -1
- package/src/core/qa-artifacts.ts +126 -0
- package/src/core/resources/contract.ts +105 -0
- package/src/core/resources/index.ts +5 -0
- package/src/core/resources/sampler.ts +590 -0
- package/src/core/resources/service-status.ts +62 -0
- package/src/core/resources/service.ts +337 -0
- package/src/core/resources/storage.ts +44 -0
- package/src/core/storage/atomic-json.ts +15 -0
- package/src/core/storage/builtins.ts +62 -6
- package/src/core/storage/logger.ts +2 -0
- package/src/core/storage/query.ts +58 -0
- package/src/core/supervisor/activity.ts +193 -0
- package/src/core/supervisor/contract.ts +312 -0
- package/src/core/supervisor/explanations.ts +104 -0
- package/src/core/supervisor/findings.ts +788 -0
- package/src/core/supervisor/history.ts +61 -0
- package/src/core/supervisor/hook-health-alerts.ts +108 -0
- package/src/core/supervisor/hook-health-storage.ts +25 -0
- package/src/core/supervisor/hook-health.ts +301 -0
- package/src/core/supervisor/hooks.ts +35 -0
- package/src/core/supervisor/index.ts +14 -0
- package/src/core/supervisor/log-feed.ts +131 -0
- package/src/core/supervisor/service.ts +511 -0
- package/src/core/supervisor/services.ts +203 -0
- package/src/core/supervisor/status.ts +59 -0
- package/src/core/supervisor/storage.ts +126 -0
- package/src/core/supervisor/timeline.ts +133 -0
- package/src/core/workflow/admission.ts +215 -0
- package/src/core/workflow/engine.ts +93 -1
- package/src/core/workflow/index.ts +10 -0
- package/src/core/workflow/proof.ts +54 -1
- package/src/core/workflow/run-state.ts +13 -1
- package/src/core/workflow/types.ts +39 -1
- package/src/lib/admission.ts +347 -0
- package/src/lib/agent-browser/client.ts +2 -10
- package/src/lib/browser/capture-fidelity.ts +98 -0
- package/src/lib/browser/client.ts +212 -10
- package/src/lib/browser/critique.ts +62 -7
- package/src/lib/browser/index.ts +65 -0
- package/src/lib/browser/page-review-judge.ts +360 -0
- package/src/lib/browser/page-review-pack.ts +2384 -0
- package/src/lib/browser/qa-run-contracts.ts +743 -0
- package/src/lib/browser/qa-run.ts +1462 -0
- package/src/lib/browser/request-diagnostics.ts +27 -0
- package/src/lib/browser/tiling.ts +32 -0
- package/src/lib/cookies/client.ts +228 -42
- package/src/lib/cookies/extra.ts +28 -0
- package/src/lib/cookies/index.ts +2 -0
- package/src/lib/coord-root-id.ts +23 -0
- package/src/lib/durable-job.ts +407 -0
- package/src/lib/http/client.ts +13 -1
- package/src/lib/instructions/apply.ts +81 -2
- package/src/lib/instructions/templates.ts +11 -2
package/src/commands/browse.ts
CHANGED
|
@@ -2,8 +2,16 @@ import { existsSync, mkdirSync, readFileSync, statSync, unlinkSync, writeFileSyn
|
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { dirname, isAbsolute, resolve } from "node:path";
|
|
4
4
|
import type { Command } from "commander";
|
|
5
|
+
import { PNG } from "pngjs";
|
|
5
6
|
import type { EmitContext, HarneryProgramContext } from "../commander.ts";
|
|
6
7
|
import { resolveBinName } from "../core/config.ts";
|
|
8
|
+
import {
|
|
9
|
+
CAPTURE_FIDELITY_MISMATCH_THRESHOLD,
|
|
10
|
+
compareBand,
|
|
11
|
+
decideFidelity,
|
|
12
|
+
type FidelityProbe,
|
|
13
|
+
pngDimensions,
|
|
14
|
+
} from "../lib/browser/capture-fidelity.ts";
|
|
7
15
|
import {
|
|
8
16
|
type AssertResult,
|
|
9
17
|
Browser,
|
|
@@ -11,6 +19,7 @@ import {
|
|
|
11
19
|
browserProxyGateFromEnv,
|
|
12
20
|
type ContentAnnotationBox,
|
|
13
21
|
type ContentChecksResult,
|
|
22
|
+
type CritiqueCoverage,
|
|
14
23
|
type CritiqueResult,
|
|
15
24
|
type CritiqueTile,
|
|
16
25
|
captureDevOverlay,
|
|
@@ -34,6 +43,16 @@ import {
|
|
|
34
43
|
writeNetscapeCookieFile,
|
|
35
44
|
wslHeadedLaunchArgs,
|
|
36
45
|
} from "../lib/browser/index.ts";
|
|
46
|
+
import {
|
|
47
|
+
findPackTile,
|
|
48
|
+
type PageReviewCaptureFidelity,
|
|
49
|
+
type PageReviewContextRecord,
|
|
50
|
+
type PageReviewExpandedTileRecord,
|
|
51
|
+
readPackContext,
|
|
52
|
+
tileId,
|
|
53
|
+
writePackContext,
|
|
54
|
+
writePackExpandedTile,
|
|
55
|
+
} from "../lib/browser/page-review-pack.ts";
|
|
37
56
|
import {
|
|
38
57
|
buildQaManifest,
|
|
39
58
|
classifySignatures,
|
|
@@ -62,7 +81,7 @@ import {
|
|
|
62
81
|
type SaveBaselineResult,
|
|
63
82
|
saveBaseline,
|
|
64
83
|
} from "../lib/browser/visual-diff.ts";
|
|
65
|
-
import { CookieJar } from "../lib/cookies/index.ts";
|
|
84
|
+
import { applyExtraCookies, CookieJar } from "../lib/cookies/index.ts";
|
|
66
85
|
|
|
67
86
|
/**
|
|
68
87
|
* `harn browse <url>`: Playwright-backed page navigation with shared
|
|
@@ -117,6 +136,8 @@ interface BrowseOpts {
|
|
|
117
136
|
store?: string;
|
|
118
137
|
profile?: string;
|
|
119
138
|
viewport?: string;
|
|
139
|
+
deviceScaleFactor?: string;
|
|
140
|
+
colorScheme?: string;
|
|
120
141
|
waitUntil: string;
|
|
121
142
|
timeout: string;
|
|
122
143
|
json?: boolean;
|
|
@@ -210,6 +231,16 @@ interface BrowseOpts {
|
|
|
210
231
|
qaStates?: string[];
|
|
211
232
|
qaReuse?: boolean;
|
|
212
233
|
qaReuseThreshold?: string;
|
|
234
|
+
// Page review pack capture (page-review-pack.ts): tiles + DOM to disk, no
|
|
235
|
+
// vision call; the judge stage reviews the pack after the browser closed.
|
|
236
|
+
reviewPack?: string;
|
|
237
|
+
reviewPackContext?: string;
|
|
238
|
+
reviewPackScope?: string[];
|
|
239
|
+
reviewPackBands?: boolean;
|
|
240
|
+
/** Re-capture ONE existing tile of --review-pack-context at --device-scale-factor. */
|
|
241
|
+
reviewPackExpand?: string;
|
|
242
|
+
/** Gate-hit rectangles (`x,y,w,h` strings) whose bands are captured past the tile cap. */
|
|
243
|
+
reviewPackHitRect?: string[];
|
|
213
244
|
// Next.js dev-overlay capture (auto-on; --no-dev-overlay opts out)
|
|
214
245
|
devOverlay?: boolean;
|
|
215
246
|
}
|
|
@@ -275,6 +306,12 @@ export function registerBrowseCommand(
|
|
|
275
306
|
"Viewport: mobile (390x844), tablet (820x1180), desktop (1280x800), hd (1920x1080), or explicit '1920x1080'",
|
|
276
307
|
"desktop",
|
|
277
308
|
)
|
|
309
|
+
.option(
|
|
310
|
+
"--color-scheme <scheme>",
|
|
311
|
+
"Emulate prefers-color-scheme for the whole session: light | dark. " +
|
|
312
|
+
"Renders theme-aware pages in that scheme without page-specific toggles. " +
|
|
313
|
+
"Absent = browser default (unchanged behavior).",
|
|
314
|
+
)
|
|
278
315
|
.option("--login", "Headed mode for one-time auth flow (cookies persist in profile)")
|
|
279
316
|
.option(
|
|
280
317
|
"--login-close-file <path>",
|
|
@@ -681,6 +718,48 @@ export function registerBrowseCommand(
|
|
|
681
718
|
"Band-diff mismatch ratio at or below which a clean tile region is reused " +
|
|
682
719
|
`(default ${DEFAULT_REUSE_MISMATCH_RATIO}; lower it toward 0 for stricter reuse).`,
|
|
683
720
|
)
|
|
721
|
+
.option(
|
|
722
|
+
"--review-pack <dir>",
|
|
723
|
+
"Capture this render into a page review pack at <dir> (contexts/<id>/: full-page " +
|
|
724
|
+
"screenshot, critique tiles as PNG files, serialized DOM, QA signature) and make no " +
|
|
725
|
+
"vision call. A judge stage (`review-pack judge`, or qa-run) reviews the tiles from disk " +
|
|
726
|
+
"after every browser has closed. Tiling knobs are the --check-critique-* flags.",
|
|
727
|
+
)
|
|
728
|
+
.option(
|
|
729
|
+
"--review-pack-context <id>",
|
|
730
|
+
"Context id inside the pack (default <viewport>-<theme>-<state> from --viewport, " +
|
|
731
|
+
"--qa-theme, --qa-state).",
|
|
732
|
+
)
|
|
733
|
+
.option(
|
|
734
|
+
"--review-pack-scope <selector>",
|
|
735
|
+
"Also tile one screenshot per element matching <selector> into the pack (repeatable).",
|
|
736
|
+
(value: string, previous: string[] = []) => [...previous, value],
|
|
737
|
+
[] as string[],
|
|
738
|
+
)
|
|
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
|
+
.option(
|
|
745
|
+
"--review-pack-expand <tile-id>",
|
|
746
|
+
"Instead of capturing a context, re-render ONE existing tile of --review-pack-context " +
|
|
747
|
+
"(e.g. T012) at --device-scale-factor and write it beside the original as " +
|
|
748
|
+
"tiles/<tile>@<dpr>x.png. Existing tiles are untouched.",
|
|
749
|
+
)
|
|
750
|
+
.option(
|
|
751
|
+
"--review-pack-hit-rect <x,y,w,h>",
|
|
752
|
+
"Document-space rectangle (integer px) a deterministic gate already flagged. Every " +
|
|
753
|
+
"full-page band that contains it is captured even past --check-critique-max-tiles, " +
|
|
754
|
+
`labelled \`hit band N\` (repeatable, at most ${REVIEW_PACK_HIT_RECT_MAX}).`,
|
|
755
|
+
(value: string, previous: string[] = []) => [...previous, value],
|
|
756
|
+
[] as string[],
|
|
757
|
+
)
|
|
758
|
+
.option(
|
|
759
|
+
"--device-scale-factor <n>",
|
|
760
|
+
"Device scale factor for the browser context (default 1). 2 renders every screenshot " +
|
|
761
|
+
"at twice the pixels; pair with --review-pack-expand for a sharper look at one tile.",
|
|
762
|
+
)
|
|
684
763
|
.option(
|
|
685
764
|
"--no-dev-overlay",
|
|
686
765
|
"Skip auto-capture of Next.js dev-overlay issues. Default: capture every queued error (kind/code/message/stack) when a <nextjs-portal> shadow root is present. Necessary because Next.js 16 + React 19 route hydration errors + most React warnings through onCaughtError → next-devtools' errorQueue, NOT through console.error, so Playwright's standard listener doesn't see them. Surfaces them in the JSON envelope under `devOverlay`.",
|
|
@@ -732,8 +811,26 @@ async function runBrowse(
|
|
|
732
811
|
opts.cookies === false
|
|
733
812
|
? null
|
|
734
813
|
: new CookieJar({ path: opts.store ?? DEFAULT_STORE, source: "harn-browse" });
|
|
814
|
+
if (jar) applyExtraCookies(url, jar, context?.extraCookies);
|
|
735
815
|
const headed = opts.login || opts.headed;
|
|
736
816
|
const viewport = parseViewport(opts.viewport ?? "desktop");
|
|
817
|
+
const deviceScaleFactor = parseDeviceScaleFactor(opts.deviceScaleFactor);
|
|
818
|
+
if (opts.reviewPackExpand !== undefined) {
|
|
819
|
+
if (opts.reviewPack === undefined || opts.reviewPackContext === undefined) {
|
|
820
|
+
throw new Error(
|
|
821
|
+
"--review-pack-expand requires --review-pack <dir> and --review-pack-context <id>.",
|
|
822
|
+
);
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
// Malformed hit rects fail here, before a browser launches.
|
|
826
|
+
parseReviewPackHitRects(opts.reviewPackHitRect);
|
|
827
|
+
let colorScheme: "light" | "dark" | undefined;
|
|
828
|
+
if (opts.colorScheme !== undefined) {
|
|
829
|
+
if (opts.colorScheme !== "light" && opts.colorScheme !== "dark") {
|
|
830
|
+
throw new Error(`--color-scheme must be light or dark (got: ${opts.colorScheme}).`);
|
|
831
|
+
}
|
|
832
|
+
colorScheme = opts.colorScheme;
|
|
833
|
+
}
|
|
737
834
|
const proxy = opts.proxyFromEnv ? browserProxyFromEnv() : undefined;
|
|
738
835
|
const proxyGate = opts.proxyFromEnv ? browserProxyGateFromEnv() : null;
|
|
739
836
|
if (opts.loginCloseFile && !opts.login) {
|
|
@@ -754,6 +851,8 @@ async function runBrowse(
|
|
|
754
851
|
headed,
|
|
755
852
|
jar,
|
|
756
853
|
viewport,
|
|
854
|
+
...(deviceScaleFactor !== undefined ? { deviceScaleFactor } : {}),
|
|
855
|
+
...(colorScheme ? { colorScheme } : {}),
|
|
757
856
|
navigationTimeout: Number.parseInt(opts.timeout, 10),
|
|
758
857
|
waitUntil: opts.waitUntil as BrowseOpts["waitUntil"] as never,
|
|
759
858
|
recordHarPath: opts.networkHar ? resolve(opts.networkHar) : undefined,
|
|
@@ -979,12 +1078,25 @@ async function runBrowse(
|
|
|
979
1078
|
"info",
|
|
980
1079
|
);
|
|
981
1080
|
}
|
|
982
|
-
critique =
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
1081
|
+
critique = {
|
|
1082
|
+
...(await runCritique({
|
|
1083
|
+
url: navResult.url,
|
|
1084
|
+
rubric,
|
|
1085
|
+
tiles: tilesToReview,
|
|
1086
|
+
provider: critiqueProvider,
|
|
1087
|
+
})),
|
|
1088
|
+
// Coverage rides on every envelope, skipped or not, so a reader can
|
|
1089
|
+
// tell a pass over the whole page from a pass over its first N bands.
|
|
1090
|
+
coverage: captured.coverage,
|
|
1091
|
+
};
|
|
1092
|
+
}
|
|
1093
|
+
// Page review pack capture: tiles, DOM, and signature to disk, no vision
|
|
1094
|
+
// call. Also before annotations, for the same reason as the critique.
|
|
1095
|
+
let reviewPack: ReviewPackReport | undefined;
|
|
1096
|
+
if (opts.reviewPack !== undefined && opts.reviewPackExpand !== undefined) {
|
|
1097
|
+
reviewPack = await expandReviewPackTile(browser, opts, deviceScaleFactor ?? 1);
|
|
1098
|
+
} else if (opts.reviewPack !== undefined) {
|
|
1099
|
+
reviewPack = await captureReviewPackContext(browser, url, navResult, opts, qaCapture);
|
|
988
1100
|
}
|
|
989
1101
|
// Persist the QA baseline AFTER critique so a passing run's verdicts ride
|
|
990
1102
|
// along with the snapshot (still before annotations mutate the page).
|
|
@@ -997,6 +1109,7 @@ async function runBrowse(
|
|
|
997
1109
|
critique?.provider &&
|
|
998
1110
|
critique.outcome !== "skipped" &&
|
|
999
1111
|
critiqueTiles !== undefined &&
|
|
1112
|
+
!critique.coverage?.capped &&
|
|
1000
1113
|
(!qaReuse || qaReuse.tiles_reused === 0);
|
|
1001
1114
|
const persistedCritique: PersistedCritique | undefined =
|
|
1002
1115
|
fullCoverage && critique && critiqueTiles
|
|
@@ -1160,6 +1273,7 @@ async function runBrowse(
|
|
|
1160
1273
|
batchResult,
|
|
1161
1274
|
qaPlan,
|
|
1162
1275
|
qaReuse ? summarizeReuse(qaReuse) : undefined,
|
|
1276
|
+
reviewPack,
|
|
1163
1277
|
);
|
|
1164
1278
|
} else {
|
|
1165
1279
|
await runTrioMode(
|
|
@@ -1180,6 +1294,7 @@ async function runBrowse(
|
|
|
1180
1294
|
batchResult,
|
|
1181
1295
|
qaPlan,
|
|
1182
1296
|
qaReuse ? summarizeReuse(qaReuse) : undefined,
|
|
1297
|
+
reviewPack,
|
|
1183
1298
|
);
|
|
1184
1299
|
}
|
|
1185
1300
|
|
|
@@ -1425,6 +1540,7 @@ async function runPrintMode(
|
|
|
1425
1540
|
batchResult: BatchResult | undefined,
|
|
1426
1541
|
qaPlan: QaPlanReport | undefined,
|
|
1427
1542
|
qaReuse: Record<string, unknown> | undefined,
|
|
1543
|
+
reviewPack: ReviewPackReport | undefined,
|
|
1428
1544
|
): Promise<void> {
|
|
1429
1545
|
let body: string | null = null;
|
|
1430
1546
|
if (opts.html) {
|
|
@@ -1461,6 +1577,7 @@ async function runPrintMode(
|
|
|
1461
1577
|
if (devOverlay) result.devOverlay = devOverlay;
|
|
1462
1578
|
if (qaPlan) result.qaPlan = qaPlan;
|
|
1463
1579
|
if (qaReuse) result.qaReuse = qaReuse;
|
|
1580
|
+
if (reviewPack) result.reviewPack = reviewPack;
|
|
1464
1581
|
if (batchResult && batchResult.clipboardReads.length > 0) {
|
|
1465
1582
|
result.batchClipboardReads = batchResult.clipboardReads;
|
|
1466
1583
|
}
|
|
@@ -1499,6 +1616,7 @@ async function runTrioMode(
|
|
|
1499
1616
|
batchResult: BatchResult | undefined,
|
|
1500
1617
|
qaPlan: QaPlanReport | undefined,
|
|
1501
1618
|
qaReuse: Record<string, unknown> | undefined,
|
|
1619
|
+
reviewPack: ReviewPackReport | undefined,
|
|
1502
1620
|
): Promise<void> {
|
|
1503
1621
|
const prefix = resolveOutPrefix(opts.out);
|
|
1504
1622
|
mkdirSync(dirname(prefix), { recursive: true });
|
|
@@ -1595,6 +1713,7 @@ async function runTrioMode(
|
|
|
1595
1713
|
if (devOverlay) envelope.devOverlay = devOverlay;
|
|
1596
1714
|
if (qaPlan) envelope.qaPlan = qaPlan;
|
|
1597
1715
|
if (qaReuse) envelope.qaReuse = qaReuse;
|
|
1716
|
+
if (reviewPack) envelope.reviewPack = reviewPack;
|
|
1598
1717
|
if (batchResult && batchResult.clipboardReads.length > 0) {
|
|
1599
1718
|
envelope.batchClipboardReads = batchResult.clipboardReads;
|
|
1600
1719
|
}
|
|
@@ -2088,12 +2207,14 @@ interface QaCaptureState {
|
|
|
2088
2207
|
* supplies the live-page inputs (signature, page height, explicit scope match
|
|
2089
2208
|
* counts). Classification and manifest math live in lib/browser/qa-plan.ts.
|
|
2090
2209
|
*/
|
|
2091
|
-
|
|
2210
|
+
/** Capture the live page's QA signature and serialized DOM for the store key
|
|
2211
|
+
* `--qa-target` (default: the url argument) and the labelled context. */
|
|
2212
|
+
async function captureQaState(
|
|
2092
2213
|
browser: Browser,
|
|
2093
2214
|
targetArg: string,
|
|
2094
2215
|
renderedUrl: string,
|
|
2095
2216
|
opts: BrowseOpts,
|
|
2096
|
-
): Promise<
|
|
2217
|
+
): Promise<QaCaptureState> {
|
|
2097
2218
|
const context: QaContext = {
|
|
2098
2219
|
viewport: opts.viewport ?? "desktop",
|
|
2099
2220
|
theme: opts.qaTheme === "dark" ? "dark" : "light",
|
|
@@ -2108,11 +2229,22 @@ async function runQaPlanning(
|
|
|
2108
2229
|
stylesheets: captured.stylesheets,
|
|
2109
2230
|
...(captured.truncated ? { truncated: true } : {}),
|
|
2110
2231
|
};
|
|
2232
|
+
return { target, context, signature, domHtml: captured.domHtml };
|
|
2233
|
+
}
|
|
2234
|
+
|
|
2235
|
+
async function runQaPlanning(
|
|
2236
|
+
browser: Browser,
|
|
2237
|
+
targetArg: string,
|
|
2238
|
+
renderedUrl: string,
|
|
2239
|
+
opts: BrowseOpts,
|
|
2240
|
+
): Promise<{ report: QaPlanReport; capture: QaCaptureState }> {
|
|
2241
|
+
const capture = await captureQaState(browser, targetArg, renderedUrl, opts);
|
|
2242
|
+
const { target, context, signature } = capture;
|
|
2111
2243
|
const report: QaPlanReport = {
|
|
2112
2244
|
signature: {
|
|
2113
|
-
nodes:
|
|
2114
|
-
stylesheets:
|
|
2115
|
-
truncated:
|
|
2245
|
+
nodes: signature.nodes.length,
|
|
2246
|
+
stylesheets: signature.stylesheets.length,
|
|
2247
|
+
truncated: Boolean(signature.truncated),
|
|
2116
2248
|
},
|
|
2117
2249
|
};
|
|
2118
2250
|
|
|
@@ -2150,20 +2282,324 @@ async function runQaPlanning(
|
|
|
2150
2282
|
});
|
|
2151
2283
|
}
|
|
2152
2284
|
|
|
2153
|
-
return { report, capture
|
|
2285
|
+
return { report, capture };
|
|
2154
2286
|
}
|
|
2155
2287
|
|
|
2156
|
-
|
|
2157
|
-
|
|
2288
|
+
/** Tiling knobs shared by the critique and the review-pack capture. The
|
|
2289
|
+
* routed provider's vision long-edge budget caps band height so tiles are
|
|
2290
|
+
* never silently downscaled by the model. */
|
|
2291
|
+
function critiqueTilingKnobs(
|
|
2158
2292
|
opts: BrowseOpts,
|
|
2159
2293
|
tileBudgetPx?: number,
|
|
2160
|
-
):
|
|
2294
|
+
): { maxTiles: number; band: number; overlap: number } {
|
|
2161
2295
|
const maxTiles = Math.max(1, Number.parseInt(opts.checkCritiqueMaxTiles ?? "24", 10));
|
|
2162
|
-
// The routed provider's vision long-edge budget caps band height so tiles
|
|
2163
|
-
// are never silently downscaled by the model.
|
|
2164
2296
|
const bandFlag = Math.max(200, Number.parseInt(opts.checkCritiqueBand ?? "1400", 10));
|
|
2165
2297
|
const band = tileBudgetPx ? Math.max(200, Math.min(bandFlag, tileBudgetPx)) : bandFlag;
|
|
2166
2298
|
const overlap = Math.max(0, Number.parseInt(opts.checkCritiqueOverlap ?? "120", 10));
|
|
2299
|
+
return { maxTiles, band, overlap };
|
|
2300
|
+
}
|
|
2301
|
+
|
|
2302
|
+
/** What `--review-pack` puts in the JSON envelope. */
|
|
2303
|
+
interface ReviewPackReport {
|
|
2304
|
+
dir: string;
|
|
2305
|
+
context_id: string;
|
|
2306
|
+
tiles: number;
|
|
2307
|
+
scopes: Array<{ selector: string; tiles: number }>;
|
|
2308
|
+
coverage: CritiqueCoverage;
|
|
2309
|
+
files: PageReviewContextRecord["files"];
|
|
2310
|
+
/** Bands captured past the tile cap for --review-pack-hit-rect rectangles. */
|
|
2311
|
+
hit_bands?: number;
|
|
2312
|
+
/** Where the tiles came from and what the fidelity probe saw. */
|
|
2313
|
+
capture_fidelity?: PageReviewCaptureFidelity;
|
|
2314
|
+
/** Capture warnings worth surfacing beside coverage (fidelity fallback, failed probes). */
|
|
2315
|
+
warnings?: string[];
|
|
2316
|
+
/** Set by --review-pack-expand: the one region that was re-captured. */
|
|
2317
|
+
expanded?: PageReviewExpandedTileRecord;
|
|
2318
|
+
}
|
|
2319
|
+
|
|
2320
|
+
/**
|
|
2321
|
+
* `--review-pack-expand`: re-render one existing tile of a pack context at
|
|
2322
|
+
* the context's device scale factor. The tile's CSS-pixel rect is captured by
|
|
2323
|
+
* scrolling the viewport to it and clipping (`captureRegionByScroll`), which
|
|
2324
|
+
* renders the region the way a reader sees it and yields tile size × DPR
|
|
2325
|
+
* pixels. A second full-page screenshot is not taken: it would reproduce the
|
|
2326
|
+
* very raster artifact the expand is meant to check. No other pack file
|
|
2327
|
+
* changes.
|
|
2328
|
+
*/
|
|
2329
|
+
async function expandReviewPackTile(
|
|
2330
|
+
browser: Browser,
|
|
2331
|
+
opts: BrowseOpts,
|
|
2332
|
+
dpr: number,
|
|
2333
|
+
): Promise<ReviewPackReport> {
|
|
2334
|
+
const packDir = opts.reviewPack as string;
|
|
2335
|
+
const contextId = opts.reviewPackContext as string;
|
|
2336
|
+
const tileId = opts.reviewPackExpand as string;
|
|
2337
|
+
const { tile } = findPackTile([readPackContext(packDir, contextId)], tileId, contextId);
|
|
2338
|
+
const region = await browser.captureRegionByScroll({
|
|
2339
|
+
x: tile.x,
|
|
2340
|
+
y: tile.scrollY,
|
|
2341
|
+
width: tile.width,
|
|
2342
|
+
height: tile.height,
|
|
2343
|
+
});
|
|
2344
|
+
const { record, expanded } = writePackExpandedTile(packDir, contextId, {
|
|
2345
|
+
tileId,
|
|
2346
|
+
dpr,
|
|
2347
|
+
region,
|
|
2348
|
+
});
|
|
2349
|
+
emit.log(
|
|
2350
|
+
`review-pack: ${record.id}/${tileId} expanded at ${dpr}× → ${expanded.width}×${expanded.height} px, ${expanded.file}`,
|
|
2351
|
+
"info",
|
|
2352
|
+
);
|
|
2353
|
+
return {
|
|
2354
|
+
dir: packDir,
|
|
2355
|
+
context_id: record.id,
|
|
2356
|
+
tiles: record.tiles.length,
|
|
2357
|
+
scopes: record.scopes,
|
|
2358
|
+
coverage: record.coverage,
|
|
2359
|
+
files: record.files,
|
|
2360
|
+
expanded,
|
|
2361
|
+
};
|
|
2362
|
+
}
|
|
2363
|
+
|
|
2364
|
+
/**
|
|
2365
|
+
* Capture-fidelity probe (capture-fidelity.ts). Every band is re-shot by
|
|
2366
|
+
* scrolling the viewport (`Browser.captureRegionByScroll`) and pixel compared
|
|
2367
|
+
* with the crop the tiler took from the full-page screenshot. When they all
|
|
2368
|
+
* agree the tiles stand and the record says `full-page`; when any disagrees,
|
|
2369
|
+
* every band is re-cut from its scrolled capture (same rects, same ids, only
|
|
2370
|
+
* the pixels change) and the record says `scrolled-bands`. A probe that
|
|
2371
|
+
* cannot run, or a re-capture that comes back the wrong size (a page wider
|
|
2372
|
+
* than the viewport), keeps the full-page crop for that band and says so in a
|
|
2373
|
+
* warning. Runs while the capture browser is open; the judge never opens one.
|
|
2374
|
+
*/
|
|
2375
|
+
async function reconcileCaptureFidelity(
|
|
2376
|
+
browser: Browser,
|
|
2377
|
+
tiles: CritiqueTile[],
|
|
2378
|
+
): Promise<{ tiles: CritiqueTile[]; fidelity: PageReviewCaptureFidelity; warnings: string[] }> {
|
|
2379
|
+
const threshold = CAPTURE_FIDELITY_MISMATCH_THRESHOLD;
|
|
2380
|
+
const warnings: string[] = [];
|
|
2381
|
+
const dpr = await browser.currentPage.evaluate(() => window.devicePixelRatio || 1);
|
|
2382
|
+
// Tile rects live in the full-page PNG's pixel space; the scroll capture
|
|
2383
|
+
// takes CSS px and renders at the page's DPR, so the two line up again.
|
|
2384
|
+
const cssRect = (tile: CritiqueTile) => ({
|
|
2385
|
+
x: (tile.x ?? 0) / dpr,
|
|
2386
|
+
y: tile.scrollY / dpr,
|
|
2387
|
+
width: tile.width / dpr,
|
|
2388
|
+
height: tile.height / dpr,
|
|
2389
|
+
});
|
|
2390
|
+
const shots = new Map<number, Buffer | undefined>();
|
|
2391
|
+
const shoot = async (position: number): Promise<Buffer | undefined> => {
|
|
2392
|
+
if (shots.has(position)) return shots.get(position);
|
|
2393
|
+
const tile = tiles[position] as CritiqueTile;
|
|
2394
|
+
let shot: Buffer | undefined;
|
|
2395
|
+
try {
|
|
2396
|
+
const captured = await browser.captureRegionByScroll(cssRect(tile));
|
|
2397
|
+
const size = pngDimensions(captured);
|
|
2398
|
+
if (Math.abs(size.width - tile.width) > 1 || Math.abs(size.height - tile.height) > 1) {
|
|
2399
|
+
warnings.push(
|
|
2400
|
+
`review-pack: scrolled capture of ${tileId(position)} came back ` +
|
|
2401
|
+
`${size.width}×${size.height} px for a ${tile.width}×${tile.height} px band; ` +
|
|
2402
|
+
"its full-page crop was kept",
|
|
2403
|
+
);
|
|
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
|
+
);
|
|
2412
|
+
}
|
|
2413
|
+
shots.set(position, shot);
|
|
2414
|
+
return shot;
|
|
2415
|
+
};
|
|
2416
|
+
// Every band is probed. A two-band sample (top and middle) missed a raster
|
|
2417
|
+
// artifact that sat elsewhere on the page, and once every band has been
|
|
2418
|
+
// shot by scrolling, the fallback below costs nothing extra: the same shots
|
|
2419
|
+
// become the tiles.
|
|
2420
|
+
const probes: FidelityProbe[] = [];
|
|
2421
|
+
for (let position = 0; position < tiles.length; position++) {
|
|
2422
|
+
const tile = tiles[position] as CritiqueTile;
|
|
2423
|
+
const shot = await shoot(position);
|
|
2424
|
+
if (!shot) continue;
|
|
2425
|
+
const cmp = compareBand(
|
|
2426
|
+
PNG.sync.read(Buffer.from(tile.pngBase64, "base64")),
|
|
2427
|
+
PNG.sync.read(shot),
|
|
2428
|
+
threshold,
|
|
2429
|
+
);
|
|
2430
|
+
probes.push({
|
|
2431
|
+
tile_id: tileId(position),
|
|
2432
|
+
scrollY: tile.scrollY,
|
|
2433
|
+
height: tile.height,
|
|
2434
|
+
mismatch_ratio: cmp.mismatch_ratio,
|
|
2435
|
+
});
|
|
2436
|
+
}
|
|
2437
|
+
const fidelity = decideFidelity(probes, threshold);
|
|
2438
|
+
if (fidelity.source === "full-page") return { tiles, fidelity, warnings };
|
|
2439
|
+
const recut: CritiqueTile[] = [];
|
|
2440
|
+
for (let position = 0; position < tiles.length; position++) {
|
|
2441
|
+
const tile = tiles[position] as CritiqueTile;
|
|
2442
|
+
const shot = await shoot(position);
|
|
2443
|
+
recut.push(shot ? { ...tile, pngBase64: shot.toString("base64") } : tile);
|
|
2444
|
+
}
|
|
2445
|
+
warnings.unshift(
|
|
2446
|
+
`review-pack: the full-page screenshot disagreed with the viewport render on ` +
|
|
2447
|
+
`${fidelity.mismatched.join(", ")} (mismatch ratio above ${threshold}); every band was ` +
|
|
2448
|
+
"re-captured by scrolling the viewport",
|
|
2449
|
+
);
|
|
2450
|
+
return { tiles: recut, fidelity, warnings };
|
|
2451
|
+
}
|
|
2452
|
+
|
|
2453
|
+
/**
|
|
2454
|
+
* Capture this render into a page review pack: one full-page screenshot,
|
|
2455
|
+
* banded tiles (unless --no-review-pack-bands), one tile set per
|
|
2456
|
+
* --review-pack-scope selector, the serialized DOM, and the QA signature —
|
|
2457
|
+
* all written to disk, with no vision call. The judge stage reads them back
|
|
2458
|
+
* once the browser is gone.
|
|
2459
|
+
*/
|
|
2460
|
+
async function captureReviewPackContext(
|
|
2461
|
+
browser: Browser,
|
|
2462
|
+
targetArg: string,
|
|
2463
|
+
navResult: { url: string; title: string },
|
|
2464
|
+
opts: BrowseOpts,
|
|
2465
|
+
qaCapture: QaCaptureState | undefined,
|
|
2466
|
+
): Promise<ReviewPackReport> {
|
|
2467
|
+
const packDir = opts.reviewPack as string;
|
|
2468
|
+
const { maxTiles, band, overlap } = critiqueTilingKnobs(opts);
|
|
2469
|
+
const buffer = await browser.fullPageScreenshotBuffer();
|
|
2470
|
+
let atoms: Awaited<ReturnType<Browser["visualAtoms"]>> | undefined;
|
|
2471
|
+
try {
|
|
2472
|
+
atoms = await browser.visualAtoms();
|
|
2473
|
+
} catch {
|
|
2474
|
+
atoms = undefined;
|
|
2475
|
+
}
|
|
2476
|
+
const tiling = { bandHeight: band, overlap, maxTiles, atoms };
|
|
2477
|
+
const withBands = opts.reviewPackBands !== false;
|
|
2478
|
+
// Gate-hit rectangles (qa-run lifts them from the gate stage, which runs
|
|
2479
|
+
// first) pull the bands they land in past the cap; those tiles follow the
|
|
2480
|
+
// kept bands as `hit band N`. Coverage still describes the kept prefix.
|
|
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",
|
|
2524
|
+
);
|
|
2525
|
+
}
|
|
2526
|
+
scopeTiles.push({ selector, tiles: scoped.tiles, coverage: scoped.coverage });
|
|
2527
|
+
}
|
|
2528
|
+
// Coverage: the bands when present, else the scopes folded together
|
|
2529
|
+
// (heights worst-case, band counts add, capped sticky).
|
|
2530
|
+
let coverage: CritiqueCoverage | undefined = banded?.coverage;
|
|
2531
|
+
if (!coverage) {
|
|
2532
|
+
for (const scope of scopeTiles) {
|
|
2533
|
+
coverage = coverage
|
|
2534
|
+
? {
|
|
2535
|
+
page_height_px: Math.max(coverage.page_height_px, scope.coverage.page_height_px),
|
|
2536
|
+
reviewed_height_px: Math.min(
|
|
2537
|
+
coverage.reviewed_height_px,
|
|
2538
|
+
scope.coverage.reviewed_height_px,
|
|
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;
|
|
2545
|
+
}
|
|
2546
|
+
}
|
|
2547
|
+
const page = await browser.currentPage.evaluate(() => ({
|
|
2548
|
+
width: Math.max(document.documentElement.scrollWidth, document.body?.scrollWidth ?? 0),
|
|
2549
|
+
height: Math.max(document.documentElement.scrollHeight, document.body?.scrollHeight ?? 0),
|
|
2550
|
+
}));
|
|
2551
|
+
const capture = qaCapture ?? (await captureQaState(browser, targetArg, navResult.url, opts));
|
|
2552
|
+
const record = writePackContext(packDir, {
|
|
2553
|
+
context: {
|
|
2554
|
+
...capture.context,
|
|
2555
|
+
...(opts.reviewPackContext ? { id: opts.reviewPackContext } : {}),
|
|
2556
|
+
},
|
|
2557
|
+
url: navResult.url,
|
|
2558
|
+
title: navResult.title,
|
|
2559
|
+
fullPage: buffer,
|
|
2560
|
+
pageWidth: page.width,
|
|
2561
|
+
pageHeight: page.height,
|
|
2562
|
+
tiles: bandTiles,
|
|
2563
|
+
coverage: coverage ?? {
|
|
2564
|
+
page_height_px: page.height,
|
|
2565
|
+
reviewed_height_px: 0,
|
|
2566
|
+
bands_total: 0,
|
|
2567
|
+
bands_reviewed: 0,
|
|
2568
|
+
capped: false,
|
|
2569
|
+
},
|
|
2570
|
+
scopeTiles: scopeTiles.map(({ selector, tiles }) => ({ selector, tiles })),
|
|
2571
|
+
signature: capture.signature,
|
|
2572
|
+
domHtml: capture.domHtml,
|
|
2573
|
+
...(captureFidelity ? { captureFidelity } : {}),
|
|
2574
|
+
hitBands: banded?.hitBands ?? 0,
|
|
2575
|
+
});
|
|
2576
|
+
emit.log(
|
|
2577
|
+
`review-pack: ${record.id} → ${record.tiles.length} tile(s)` +
|
|
2578
|
+
(record.scopes.length > 0
|
|
2579
|
+
? ` (${record.scopes.map((s) => `${s.selector}: ${s.tiles}`).join(", ")})`
|
|
2580
|
+
: "") +
|
|
2581
|
+
` written to ${packDir}`,
|
|
2582
|
+
"info",
|
|
2583
|
+
);
|
|
2584
|
+
return {
|
|
2585
|
+
dir: packDir,
|
|
2586
|
+
context_id: record.id,
|
|
2587
|
+
tiles: record.tiles.length,
|
|
2588
|
+
scopes: record.scopes,
|
|
2589
|
+
coverage: record.coverage,
|
|
2590
|
+
files: record.files,
|
|
2591
|
+
...(record.hit_bands !== undefined ? { hit_bands: record.hit_bands } : {}),
|
|
2592
|
+
...(record.capture_fidelity ? { capture_fidelity: record.capture_fidelity } : {}),
|
|
2593
|
+
...(fidelityWarnings.length > 0 ? { warnings: fidelityWarnings } : {}),
|
|
2594
|
+
};
|
|
2595
|
+
}
|
|
2596
|
+
|
|
2597
|
+
async function captureCritiqueTiles(
|
|
2598
|
+
browser: Browser,
|
|
2599
|
+
opts: BrowseOpts,
|
|
2600
|
+
tileBudgetPx?: number,
|
|
2601
|
+
): Promise<{ tiles: CritiqueTile[]; fullPage: Buffer; coverage: CritiqueCoverage }> {
|
|
2602
|
+
const { maxTiles, band, overlap } = critiqueTilingKnobs(opts, tileBudgetPx);
|
|
2167
2603
|
|
|
2168
2604
|
// Capture the whole page ONCE, then crop tiles from the pixels. Playwright's
|
|
2169
2605
|
// clip is viewport-relative, so a below-fold band clip throws ("clipped area
|
|
@@ -2183,7 +2619,7 @@ async function captureCritiqueTiles(
|
|
|
2183
2619
|
} catch {
|
|
2184
2620
|
atoms = undefined;
|
|
2185
2621
|
}
|
|
2186
|
-
const tiles = tilesFromFullPage(buffer, {
|
|
2622
|
+
const { tiles, coverage } = tilesFromFullPage(buffer, {
|
|
2187
2623
|
elementRects,
|
|
2188
2624
|
bandHeight: band,
|
|
2189
2625
|
overlap,
|
|
@@ -2202,7 +2638,7 @@ async function captureCritiqueTiles(
|
|
|
2202
2638
|
"warn",
|
|
2203
2639
|
);
|
|
2204
2640
|
}
|
|
2205
|
-
return { tiles, fullPage: buffer };
|
|
2641
|
+
return { tiles, fullPage: buffer, coverage };
|
|
2206
2642
|
}
|
|
2207
2643
|
function logCritiqueSummary(critique: CritiqueResult): void {
|
|
2208
2644
|
if (critique.outcome === "skipped") {
|
|
@@ -2299,6 +2735,46 @@ async function waitForTerminalEnter(signal: AbortSignal): Promise<void> {
|
|
|
2299
2735
|
});
|
|
2300
2736
|
}
|
|
2301
2737
|
|
|
2738
|
+
/** Upper bound on --review-pack-hit-rect values (and on hit bands cut past the cap). */
|
|
2739
|
+
const REVIEW_PACK_HIT_RECT_MAX = 50;
|
|
2740
|
+
|
|
2741
|
+
/** `x,y,w,h` integer rectangles from repeated --review-pack-hit-rect flags. */
|
|
2742
|
+
function parseReviewPackHitRects(
|
|
2743
|
+
specs: string[] | undefined,
|
|
2744
|
+
): Array<{ x: number; y: number; width: number; height: number }> {
|
|
2745
|
+
const list = specs ?? [];
|
|
2746
|
+
if (list.length > REVIEW_PACK_HIT_RECT_MAX) {
|
|
2747
|
+
throw new Error(
|
|
2748
|
+
`--review-pack-hit-rect accepts at most ${REVIEW_PACK_HIT_RECT_MAX} rectangles (got ${list.length}).`,
|
|
2749
|
+
);
|
|
2750
|
+
}
|
|
2751
|
+
return list.map((spec) => {
|
|
2752
|
+
const parts = spec.split(",").map((part) => part.trim());
|
|
2753
|
+
const nums = parts.map((part) => (/^-?\d+$/.test(part) ? Number.parseInt(part, 10) : NaN));
|
|
2754
|
+
if (parts.length !== 4 || nums.some((n) => !Number.isFinite(n))) {
|
|
2755
|
+
throw new Error(
|
|
2756
|
+
`--review-pack-hit-rect must be four integers x,y,w,h in document px (got: ${spec}).`,
|
|
2757
|
+
);
|
|
2758
|
+
}
|
|
2759
|
+
const [x, y, width, height] = nums as [number, number, number, number];
|
|
2760
|
+
if (width < 0 || height < 0) {
|
|
2761
|
+
throw new Error(
|
|
2762
|
+
`--review-pack-hit-rect width and height must not be negative (got: ${spec}).`,
|
|
2763
|
+
);
|
|
2764
|
+
}
|
|
2765
|
+
return { x, y, width, height };
|
|
2766
|
+
});
|
|
2767
|
+
}
|
|
2768
|
+
|
|
2769
|
+
function parseDeviceScaleFactor(spec: string | undefined): number | undefined {
|
|
2770
|
+
if (spec === undefined) return undefined;
|
|
2771
|
+
const value = Number.parseFloat(spec);
|
|
2772
|
+
if (!Number.isFinite(value) || value < 1 || value > 4) {
|
|
2773
|
+
throw new Error(`--device-scale-factor must be a number between 1 and 4 (got: ${spec})`);
|
|
2774
|
+
}
|
|
2775
|
+
return value;
|
|
2776
|
+
}
|
|
2777
|
+
|
|
2302
2778
|
function parseViewport(spec: string): { width: number; height: number } {
|
|
2303
2779
|
const preset = VIEWPORT_PRESETS[spec.toLowerCase()];
|
|
2304
2780
|
if (preset) return preset;
|