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/dist/commands/browse.js
CHANGED
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, readFileSync, statSync, unlinkSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { dirname, isAbsolute, resolve } from "node:path";
|
|
4
|
+
import { PNG } from "pngjs";
|
|
4
5
|
import { resolveBinName } from "../core/config.js";
|
|
6
|
+
import { CAPTURE_FIDELITY_MISMATCH_THRESHOLD, compareBand, decideFidelity, pngDimensions, } from "../lib/browser/capture-fidelity.js";
|
|
5
7
|
import { Browser, browserProxyFromEnv, browserProxyGateFromEnv, captureDevOverlay, DEFAULT_CRITIQUE_RUBRIC, extractObservedIp, parseAssertSpec, runCritique, tilesFromFullPage, WEBRTC_PROXY_ONLY_ARG, writeNetscapeCookieFile, wslHeadedLaunchArgs, } from "../lib/browser/index.js";
|
|
8
|
+
import { findPackTile, readPackContext, tileId, writePackContext, writePackExpandedTile, } from "../lib/browser/page-review-pack.js";
|
|
6
9
|
import { buildQaManifest, classifySignatures, } from "../lib/browser/qa-plan.js";
|
|
7
10
|
import { DEFAULT_REUSE_MISMATCH_RATIO, planCritiqueReuse, QA_CRITIQUE_CONTRACT_VERSION, rubricDigest, } from "../lib/browser/qa-reuse.js";
|
|
8
11
|
import { loadQaSnapshot, resolveQaBaseline, saveQaSnapshot } from "../lib/browser/qa-snapshot.js";
|
|
9
12
|
import { startBrowserSessionServer, } from "../lib/browser/session-control.js";
|
|
10
13
|
import { diffAgainstBaseline, saveBaseline, } from "../lib/browser/visual-diff.js";
|
|
11
|
-
import { CookieJar } from "../lib/cookies/index.js";
|
|
14
|
+
import { applyExtraCookies, CookieJar } from "../lib/cookies/index.js";
|
|
12
15
|
/**
|
|
13
16
|
* `harn browse <url>`: Playwright-backed page navigation with shared
|
|
14
17
|
* cookie jar + persistent profile + diagnostics capture for LLM
|
|
@@ -64,6 +67,9 @@ export function registerBrowseCommand(program, emitParam, context, binName = res
|
|
|
64
67
|
'Example: `--batch "click button; wait 1500; reload; wait 3000"`. `reload` preserves sessionStorage + cookies, which is how to repro sessionStorage-restored UI state.')
|
|
65
68
|
.option("--network-har <path>", "Record network traffic to a HAR file (finalized on close)")
|
|
66
69
|
.option("--viewport <preset|WxH>", "Viewport: mobile (390x844), tablet (820x1180), desktop (1280x800), hd (1920x1080), or explicit '1920x1080'", "desktop")
|
|
70
|
+
.option("--color-scheme <scheme>", "Emulate prefers-color-scheme for the whole session: light | dark. " +
|
|
71
|
+
"Renders theme-aware pages in that scheme without page-specific toggles. " +
|
|
72
|
+
"Absent = browser default (unchanged behavior).")
|
|
67
73
|
.option("--login", "Headed mode for one-time auth flow (cookies persist in profile)")
|
|
68
74
|
.option("--login-close-file <path>", "With --login, wait for this file instead of terminal Enter, then remove it and close cleanly")
|
|
69
75
|
.option("--control-file <path>", "With --login, publish an owner-only descriptor for repeated browse-session control")
|
|
@@ -234,6 +240,23 @@ export function registerBrowseCommand(program, emitParam, context, binName = res
|
|
|
234
240
|
"full fresh review, never an error. Stats land under `qaReuse` in the JSON envelope.")
|
|
235
241
|
.option("--qa-reuse-threshold <ratio>", "Band-diff mismatch ratio at or below which a clean tile region is reused " +
|
|
236
242
|
`(default ${DEFAULT_REUSE_MISMATCH_RATIO}; lower it toward 0 for stricter reuse).`)
|
|
243
|
+
.option("--review-pack <dir>", "Capture this render into a page review pack at <dir> (contexts/<id>/: full-page " +
|
|
244
|
+
"screenshot, critique tiles as PNG files, serialized DOM, QA signature) and make no " +
|
|
245
|
+
"vision call. A judge stage (`review-pack judge`, or qa-run) reviews the tiles from disk " +
|
|
246
|
+
"after every browser has closed. Tiling knobs are the --check-critique-* flags.")
|
|
247
|
+
.option("--review-pack-context <id>", "Context id inside the pack (default <viewport>-<theme>-<state> from --viewport, " +
|
|
248
|
+
"--qa-theme, --qa-state).")
|
|
249
|
+
.option("--review-pack-scope <selector>", "Also tile one screenshot per element matching <selector> into the pack (repeatable).", (value, previous = []) => [...previous, value], [])
|
|
250
|
+
.option("--no-review-pack-bands", "Skip full-page bands in the pack and keep only --review-pack-scope tiles (a scoped " +
|
|
251
|
+
"visual pass).")
|
|
252
|
+
.option("--review-pack-expand <tile-id>", "Instead of capturing a context, re-render ONE existing tile of --review-pack-context " +
|
|
253
|
+
"(e.g. T012) at --device-scale-factor and write it beside the original as " +
|
|
254
|
+
"tiles/<tile>@<dpr>x.png. Existing tiles are untouched.")
|
|
255
|
+
.option("--review-pack-hit-rect <x,y,w,h>", "Document-space rectangle (integer px) a deterministic gate already flagged. Every " +
|
|
256
|
+
"full-page band that contains it is captured even past --check-critique-max-tiles, " +
|
|
257
|
+
`labelled \`hit band N\` (repeatable, at most ${REVIEW_PACK_HIT_RECT_MAX}).`, (value, previous = []) => [...previous, value], [])
|
|
258
|
+
.option("--device-scale-factor <n>", "Device scale factor for the browser context (default 1). 2 renders every screenshot " +
|
|
259
|
+
"at twice the pixels; pair with --review-pack-expand for a sharper look at one tile.")
|
|
237
260
|
.option("--no-dev-overlay", "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`.")
|
|
238
261
|
.action(async (url, opts) => {
|
|
239
262
|
try {
|
|
@@ -263,8 +286,25 @@ async function runBrowse(url, opts, context) {
|
|
|
263
286
|
const jar = opts.cookies === false
|
|
264
287
|
? null
|
|
265
288
|
: new CookieJar({ path: opts.store ?? DEFAULT_STORE, source: "harn-browse" });
|
|
289
|
+
if (jar)
|
|
290
|
+
applyExtraCookies(url, jar, context?.extraCookies);
|
|
266
291
|
const headed = opts.login || opts.headed;
|
|
267
292
|
const viewport = parseViewport(opts.viewport ?? "desktop");
|
|
293
|
+
const deviceScaleFactor = parseDeviceScaleFactor(opts.deviceScaleFactor);
|
|
294
|
+
if (opts.reviewPackExpand !== undefined) {
|
|
295
|
+
if (opts.reviewPack === undefined || opts.reviewPackContext === undefined) {
|
|
296
|
+
throw new Error("--review-pack-expand requires --review-pack <dir> and --review-pack-context <id>.");
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
// Malformed hit rects fail here, before a browser launches.
|
|
300
|
+
parseReviewPackHitRects(opts.reviewPackHitRect);
|
|
301
|
+
let colorScheme;
|
|
302
|
+
if (opts.colorScheme !== undefined) {
|
|
303
|
+
if (opts.colorScheme !== "light" && opts.colorScheme !== "dark") {
|
|
304
|
+
throw new Error(`--color-scheme must be light or dark (got: ${opts.colorScheme}).`);
|
|
305
|
+
}
|
|
306
|
+
colorScheme = opts.colorScheme;
|
|
307
|
+
}
|
|
268
308
|
const proxy = opts.proxyFromEnv ? browserProxyFromEnv() : undefined;
|
|
269
309
|
const proxyGate = opts.proxyFromEnv ? browserProxyGateFromEnv() : null;
|
|
270
310
|
if (opts.loginCloseFile && !opts.login) {
|
|
@@ -282,6 +322,8 @@ async function runBrowse(url, opts, context) {
|
|
|
282
322
|
headed,
|
|
283
323
|
jar,
|
|
284
324
|
viewport,
|
|
325
|
+
...(deviceScaleFactor !== undefined ? { deviceScaleFactor } : {}),
|
|
326
|
+
...(colorScheme ? { colorScheme } : {}),
|
|
285
327
|
navigationTimeout: Number.parseInt(opts.timeout, 10),
|
|
286
328
|
waitUntil: opts.waitUntil,
|
|
287
329
|
recordHarPath: opts.networkHar ? resolve(opts.networkHar) : undefined,
|
|
@@ -482,12 +524,26 @@ async function runBrowse(url, opts, context) {
|
|
|
482
524
|
emit.log(`qa-reuse: ${qaReuse.tiles_reused}/${qaReuse.tiles_total} tiles reused (band-diff), ` +
|
|
483
525
|
`${qaReuse.tiles_reviewed} reviewed fresh${qaReuse.invalidation ? ` — ${qaReuse.invalidation}` : ""}`, "info");
|
|
484
526
|
}
|
|
485
|
-
critique =
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
527
|
+
critique = {
|
|
528
|
+
...(await runCritique({
|
|
529
|
+
url: navResult.url,
|
|
530
|
+
rubric,
|
|
531
|
+
tiles: tilesToReview,
|
|
532
|
+
provider: critiqueProvider,
|
|
533
|
+
})),
|
|
534
|
+
// Coverage rides on every envelope, skipped or not, so a reader can
|
|
535
|
+
// tell a pass over the whole page from a pass over its first N bands.
|
|
536
|
+
coverage: captured.coverage,
|
|
537
|
+
};
|
|
538
|
+
}
|
|
539
|
+
// Page review pack capture: tiles, DOM, and signature to disk, no vision
|
|
540
|
+
// call. Also before annotations, for the same reason as the critique.
|
|
541
|
+
let reviewPack;
|
|
542
|
+
if (opts.reviewPack !== undefined && opts.reviewPackExpand !== undefined) {
|
|
543
|
+
reviewPack = await expandReviewPackTile(browser, opts, deviceScaleFactor ?? 1);
|
|
544
|
+
}
|
|
545
|
+
else if (opts.reviewPack !== undefined) {
|
|
546
|
+
reviewPack = await captureReviewPackContext(browser, url, navResult, opts, qaCapture);
|
|
491
547
|
}
|
|
492
548
|
// Persist the QA baseline AFTER critique so a passing run's verdicts ride
|
|
493
549
|
// along with the snapshot (still before annotations mutate the page).
|
|
@@ -499,6 +555,7 @@ async function runBrowse(url, opts, context) {
|
|
|
499
555
|
const fullCoverage = critique?.provider &&
|
|
500
556
|
critique.outcome !== "skipped" &&
|
|
501
557
|
critiqueTiles !== undefined &&
|
|
558
|
+
!critique.coverage?.capped &&
|
|
502
559
|
(!qaReuse || qaReuse.tiles_reused === 0);
|
|
503
560
|
const persistedCritique = fullCoverage && critique && critiqueTiles
|
|
504
561
|
? {
|
|
@@ -620,10 +677,10 @@ async function runBrowse(url, opts, context) {
|
|
|
620
677
|
if (opts.baseline || opts.diff) {
|
|
621
678
|
throw new Error("--baseline / --diff require trio mode (screenshot file). Remove --snapshot/--html/--json or capture the screenshot first.");
|
|
622
679
|
}
|
|
623
|
-
await runPrintMode(browser, navResult, opts, evalResult, visibility, widths, overflow, runts, layoutLint, hit, content, critique, asserts, devOverlay, batchResult, qaPlan, qaReuse ? summarizeReuse(qaReuse) : undefined);
|
|
680
|
+
await runPrintMode(browser, navResult, opts, evalResult, visibility, widths, overflow, runts, layoutLint, hit, content, critique, asserts, devOverlay, batchResult, qaPlan, qaReuse ? summarizeReuse(qaReuse) : undefined, reviewPack);
|
|
624
681
|
}
|
|
625
682
|
else {
|
|
626
|
-
await runTrioMode(browser, navResult, opts, evalResult, visibility, widths, overflow, runts, layoutLint, hit, content, critique, asserts, devOverlay, batchResult, qaPlan, qaReuse ? summarizeReuse(qaReuse) : undefined);
|
|
683
|
+
await runTrioMode(browser, navResult, opts, evalResult, visibility, widths, overflow, runts, layoutLint, hit, content, critique, asserts, devOverlay, batchResult, qaPlan, qaReuse ? summarizeReuse(qaReuse) : undefined, reviewPack);
|
|
627
684
|
}
|
|
628
685
|
if (visibility && opts.checkVisibleAnnotate !== false) {
|
|
629
686
|
await browser.clearVisibilityAnnotations();
|
|
@@ -821,7 +878,7 @@ function splitBatchSteps(input) {
|
|
|
821
878
|
// ---------------------------------------------------------------------------
|
|
822
879
|
// Print mode (--snapshot / --html / --json): stdout, no file writes
|
|
823
880
|
// ---------------------------------------------------------------------------
|
|
824
|
-
async function runPrintMode(browser, navResult, opts, evalResult, visibility, widths, overflow, runts, layoutLint, hit, content, critique, asserts, devOverlay, batchResult, qaPlan, qaReuse) {
|
|
881
|
+
async function runPrintMode(browser, navResult, opts, evalResult, visibility, widths, overflow, runts, layoutLint, hit, content, critique, asserts, devOverlay, batchResult, qaPlan, qaReuse, reviewPack) {
|
|
825
882
|
let body = null;
|
|
826
883
|
if (opts.html) {
|
|
827
884
|
body = await browser.htmlContent(opts.selector);
|
|
@@ -871,6 +928,8 @@ async function runPrintMode(browser, navResult, opts, evalResult, visibility, wi
|
|
|
871
928
|
result.qaPlan = qaPlan;
|
|
872
929
|
if (qaReuse)
|
|
873
930
|
result.qaReuse = qaReuse;
|
|
931
|
+
if (reviewPack)
|
|
932
|
+
result.reviewPack = reviewPack;
|
|
874
933
|
if (batchResult && batchResult.clipboardReads.length > 0) {
|
|
875
934
|
result.batchClipboardReads = batchResult.clipboardReads;
|
|
876
935
|
}
|
|
@@ -892,7 +951,7 @@ async function runPrintMode(browser, navResult, opts, evalResult, visibility, wi
|
|
|
892
951
|
// ---------------------------------------------------------------------------
|
|
893
952
|
// Trio mode (default): write <prefix>.{png,html,json}
|
|
894
953
|
// ---------------------------------------------------------------------------
|
|
895
|
-
async function runTrioMode(browser, navResult, opts, evalResult, visibility, widths, overflow, runts, layoutLint, hit, content, critique, asserts, devOverlay, batchResult, qaPlan, qaReuse) {
|
|
954
|
+
async function runTrioMode(browser, navResult, opts, evalResult, visibility, widths, overflow, runts, layoutLint, hit, content, critique, asserts, devOverlay, batchResult, qaPlan, qaReuse, reviewPack) {
|
|
896
955
|
const prefix = resolveOutPrefix(opts.out);
|
|
897
956
|
mkdirSync(dirname(prefix), { recursive: true });
|
|
898
957
|
const written = [];
|
|
@@ -1002,6 +1061,8 @@ async function runTrioMode(browser, navResult, opts, evalResult, visibility, wid
|
|
|
1002
1061
|
envelope.qaPlan = qaPlan;
|
|
1003
1062
|
if (qaReuse)
|
|
1004
1063
|
envelope.qaReuse = qaReuse;
|
|
1064
|
+
if (reviewPack)
|
|
1065
|
+
envelope.reviewPack = reviewPack;
|
|
1005
1066
|
if (batchResult && batchResult.clipboardReads.length > 0) {
|
|
1006
1067
|
envelope.batchClipboardReads = batchResult.clipboardReads;
|
|
1007
1068
|
}
|
|
@@ -1377,7 +1438,9 @@ function summarizeReuse(plan) {
|
|
|
1377
1438
|
* supplies the live-page inputs (signature, page height, explicit scope match
|
|
1378
1439
|
* counts). Classification and manifest math live in lib/browser/qa-plan.ts.
|
|
1379
1440
|
*/
|
|
1380
|
-
|
|
1441
|
+
/** Capture the live page's QA signature and serialized DOM for the store key
|
|
1442
|
+
* `--qa-target` (default: the url argument) and the labelled context. */
|
|
1443
|
+
async function captureQaState(browser, targetArg, renderedUrl, opts) {
|
|
1381
1444
|
const context = {
|
|
1382
1445
|
viewport: opts.viewport ?? "desktop",
|
|
1383
1446
|
theme: opts.qaTheme === "dark" ? "dark" : "light",
|
|
@@ -1392,11 +1455,16 @@ async function runQaPlanning(browser, targetArg, renderedUrl, opts) {
|
|
|
1392
1455
|
stylesheets: captured.stylesheets,
|
|
1393
1456
|
...(captured.truncated ? { truncated: true } : {}),
|
|
1394
1457
|
};
|
|
1458
|
+
return { target, context, signature, domHtml: captured.domHtml };
|
|
1459
|
+
}
|
|
1460
|
+
async function runQaPlanning(browser, targetArg, renderedUrl, opts) {
|
|
1461
|
+
const capture = await captureQaState(browser, targetArg, renderedUrl, opts);
|
|
1462
|
+
const { target, context, signature } = capture;
|
|
1395
1463
|
const report = {
|
|
1396
1464
|
signature: {
|
|
1397
|
-
nodes:
|
|
1398
|
-
stylesheets:
|
|
1399
|
-
truncated:
|
|
1465
|
+
nodes: signature.nodes.length,
|
|
1466
|
+
stylesheets: signature.stylesheets.length,
|
|
1467
|
+
truncated: Boolean(signature.truncated),
|
|
1400
1468
|
},
|
|
1401
1469
|
};
|
|
1402
1470
|
if (opts.qaPlan) {
|
|
@@ -1424,15 +1492,256 @@ async function runQaPlanning(browser, targetArg, renderedUrl, opts) {
|
|
|
1424
1492
|
estimatedFullPageTiles,
|
|
1425
1493
|
});
|
|
1426
1494
|
}
|
|
1427
|
-
return { report, capture
|
|
1495
|
+
return { report, capture };
|
|
1428
1496
|
}
|
|
1429
|
-
|
|
1497
|
+
/** Tiling knobs shared by the critique and the review-pack capture. The
|
|
1498
|
+
* routed provider's vision long-edge budget caps band height so tiles are
|
|
1499
|
+
* never silently downscaled by the model. */
|
|
1500
|
+
function critiqueTilingKnobs(opts, tileBudgetPx) {
|
|
1430
1501
|
const maxTiles = Math.max(1, Number.parseInt(opts.checkCritiqueMaxTiles ?? "24", 10));
|
|
1431
|
-
// The routed provider's vision long-edge budget caps band height so tiles
|
|
1432
|
-
// are never silently downscaled by the model.
|
|
1433
1502
|
const bandFlag = Math.max(200, Number.parseInt(opts.checkCritiqueBand ?? "1400", 10));
|
|
1434
1503
|
const band = tileBudgetPx ? Math.max(200, Math.min(bandFlag, tileBudgetPx)) : bandFlag;
|
|
1435
1504
|
const overlap = Math.max(0, Number.parseInt(opts.checkCritiqueOverlap ?? "120", 10));
|
|
1505
|
+
return { maxTiles, band, overlap };
|
|
1506
|
+
}
|
|
1507
|
+
/**
|
|
1508
|
+
* `--review-pack-expand`: re-render one existing tile of a pack context at
|
|
1509
|
+
* the context's device scale factor. The tile's CSS-pixel rect is captured by
|
|
1510
|
+
* scrolling the viewport to it and clipping (`captureRegionByScroll`), which
|
|
1511
|
+
* renders the region the way a reader sees it and yields tile size × DPR
|
|
1512
|
+
* pixels. A second full-page screenshot is not taken: it would reproduce the
|
|
1513
|
+
* very raster artifact the expand is meant to check. No other pack file
|
|
1514
|
+
* changes.
|
|
1515
|
+
*/
|
|
1516
|
+
async function expandReviewPackTile(browser, opts, dpr) {
|
|
1517
|
+
const packDir = opts.reviewPack;
|
|
1518
|
+
const contextId = opts.reviewPackContext;
|
|
1519
|
+
const tileId = opts.reviewPackExpand;
|
|
1520
|
+
const { tile } = findPackTile([readPackContext(packDir, contextId)], tileId, contextId);
|
|
1521
|
+
const region = await browser.captureRegionByScroll({
|
|
1522
|
+
x: tile.x,
|
|
1523
|
+
y: tile.scrollY,
|
|
1524
|
+
width: tile.width,
|
|
1525
|
+
height: tile.height,
|
|
1526
|
+
});
|
|
1527
|
+
const { record, expanded } = writePackExpandedTile(packDir, contextId, {
|
|
1528
|
+
tileId,
|
|
1529
|
+
dpr,
|
|
1530
|
+
region,
|
|
1531
|
+
});
|
|
1532
|
+
emit.log(`review-pack: ${record.id}/${tileId} expanded at ${dpr}× → ${expanded.width}×${expanded.height} px, ${expanded.file}`, "info");
|
|
1533
|
+
return {
|
|
1534
|
+
dir: packDir,
|
|
1535
|
+
context_id: record.id,
|
|
1536
|
+
tiles: record.tiles.length,
|
|
1537
|
+
scopes: record.scopes,
|
|
1538
|
+
coverage: record.coverage,
|
|
1539
|
+
files: record.files,
|
|
1540
|
+
expanded,
|
|
1541
|
+
};
|
|
1542
|
+
}
|
|
1543
|
+
/**
|
|
1544
|
+
* Capture-fidelity probe (capture-fidelity.ts). Every band is re-shot by
|
|
1545
|
+
* scrolling the viewport (`Browser.captureRegionByScroll`) and pixel compared
|
|
1546
|
+
* with the crop the tiler took from the full-page screenshot. When they all
|
|
1547
|
+
* agree the tiles stand and the record says `full-page`; when any disagrees,
|
|
1548
|
+
* every band is re-cut from its scrolled capture (same rects, same ids, only
|
|
1549
|
+
* the pixels change) and the record says `scrolled-bands`. A probe that
|
|
1550
|
+
* cannot run, or a re-capture that comes back the wrong size (a page wider
|
|
1551
|
+
* than the viewport), keeps the full-page crop for that band and says so in a
|
|
1552
|
+
* warning. Runs while the capture browser is open; the judge never opens one.
|
|
1553
|
+
*/
|
|
1554
|
+
async function reconcileCaptureFidelity(browser, tiles) {
|
|
1555
|
+
const threshold = CAPTURE_FIDELITY_MISMATCH_THRESHOLD;
|
|
1556
|
+
const warnings = [];
|
|
1557
|
+
const dpr = await browser.currentPage.evaluate(() => window.devicePixelRatio || 1);
|
|
1558
|
+
// Tile rects live in the full-page PNG's pixel space; the scroll capture
|
|
1559
|
+
// takes CSS px and renders at the page's DPR, so the two line up again.
|
|
1560
|
+
const cssRect = (tile) => ({
|
|
1561
|
+
x: (tile.x ?? 0) / dpr,
|
|
1562
|
+
y: tile.scrollY / dpr,
|
|
1563
|
+
width: tile.width / dpr,
|
|
1564
|
+
height: tile.height / dpr,
|
|
1565
|
+
});
|
|
1566
|
+
const shots = new Map();
|
|
1567
|
+
const shoot = async (position) => {
|
|
1568
|
+
if (shots.has(position))
|
|
1569
|
+
return shots.get(position);
|
|
1570
|
+
const tile = tiles[position];
|
|
1571
|
+
let shot;
|
|
1572
|
+
try {
|
|
1573
|
+
const captured = await browser.captureRegionByScroll(cssRect(tile));
|
|
1574
|
+
const size = pngDimensions(captured);
|
|
1575
|
+
if (Math.abs(size.width - tile.width) > 1 || Math.abs(size.height - tile.height) > 1) {
|
|
1576
|
+
warnings.push(`review-pack: scrolled capture of ${tileId(position)} came back ` +
|
|
1577
|
+
`${size.width}×${size.height} px for a ${tile.width}×${tile.height} px band; ` +
|
|
1578
|
+
"its full-page crop was kept");
|
|
1579
|
+
}
|
|
1580
|
+
else {
|
|
1581
|
+
shot = captured;
|
|
1582
|
+
}
|
|
1583
|
+
}
|
|
1584
|
+
catch (err) {
|
|
1585
|
+
warnings.push(`review-pack: scrolled capture of ${tileId(position)} failed ` +
|
|
1586
|
+
`(${err instanceof Error ? err.message : String(err)}); its full-page crop was kept`);
|
|
1587
|
+
}
|
|
1588
|
+
shots.set(position, shot);
|
|
1589
|
+
return shot;
|
|
1590
|
+
};
|
|
1591
|
+
// Every band is probed. A two-band sample (top and middle) missed a raster
|
|
1592
|
+
// artifact that sat elsewhere on the page, and once every band has been
|
|
1593
|
+
// shot by scrolling, the fallback below costs nothing extra: the same shots
|
|
1594
|
+
// become the tiles.
|
|
1595
|
+
const probes = [];
|
|
1596
|
+
for (let position = 0; position < tiles.length; position++) {
|
|
1597
|
+
const tile = tiles[position];
|
|
1598
|
+
const shot = await shoot(position);
|
|
1599
|
+
if (!shot)
|
|
1600
|
+
continue;
|
|
1601
|
+
const cmp = compareBand(PNG.sync.read(Buffer.from(tile.pngBase64, "base64")), PNG.sync.read(shot), threshold);
|
|
1602
|
+
probes.push({
|
|
1603
|
+
tile_id: tileId(position),
|
|
1604
|
+
scrollY: tile.scrollY,
|
|
1605
|
+
height: tile.height,
|
|
1606
|
+
mismatch_ratio: cmp.mismatch_ratio,
|
|
1607
|
+
});
|
|
1608
|
+
}
|
|
1609
|
+
const fidelity = decideFidelity(probes, threshold);
|
|
1610
|
+
if (fidelity.source === "full-page")
|
|
1611
|
+
return { tiles, fidelity, warnings };
|
|
1612
|
+
const recut = [];
|
|
1613
|
+
for (let position = 0; position < tiles.length; position++) {
|
|
1614
|
+
const tile = tiles[position];
|
|
1615
|
+
const shot = await shoot(position);
|
|
1616
|
+
recut.push(shot ? { ...tile, pngBase64: shot.toString("base64") } : tile);
|
|
1617
|
+
}
|
|
1618
|
+
warnings.unshift(`review-pack: the full-page screenshot disagreed with the viewport render on ` +
|
|
1619
|
+
`${fidelity.mismatched.join(", ")} (mismatch ratio above ${threshold}); every band was ` +
|
|
1620
|
+
"re-captured by scrolling the viewport");
|
|
1621
|
+
return { tiles: recut, fidelity, warnings };
|
|
1622
|
+
}
|
|
1623
|
+
/**
|
|
1624
|
+
* Capture this render into a page review pack: one full-page screenshot,
|
|
1625
|
+
* banded tiles (unless --no-review-pack-bands), one tile set per
|
|
1626
|
+
* --review-pack-scope selector, the serialized DOM, and the QA signature —
|
|
1627
|
+
* all written to disk, with no vision call. The judge stage reads them back
|
|
1628
|
+
* once the browser is gone.
|
|
1629
|
+
*/
|
|
1630
|
+
async function captureReviewPackContext(browser, targetArg, navResult, opts, qaCapture) {
|
|
1631
|
+
const packDir = opts.reviewPack;
|
|
1632
|
+
const { maxTiles, band, overlap } = critiqueTilingKnobs(opts);
|
|
1633
|
+
const buffer = await browser.fullPageScreenshotBuffer();
|
|
1634
|
+
let atoms;
|
|
1635
|
+
try {
|
|
1636
|
+
atoms = await browser.visualAtoms();
|
|
1637
|
+
}
|
|
1638
|
+
catch {
|
|
1639
|
+
atoms = undefined;
|
|
1640
|
+
}
|
|
1641
|
+
const tiling = { bandHeight: band, overlap, maxTiles, atoms };
|
|
1642
|
+
const withBands = opts.reviewPackBands !== false;
|
|
1643
|
+
// Gate-hit rectangles (qa-run lifts them from the gate stage, which runs
|
|
1644
|
+
// first) pull the bands they land in past the cap; those tiles follow the
|
|
1645
|
+
// kept bands as `hit band N`. Coverage still describes the kept prefix.
|
|
1646
|
+
const hitRects = parseReviewPackHitRects(opts.reviewPackHitRect);
|
|
1647
|
+
const banded = withBands
|
|
1648
|
+
? tilesFromFullPage(buffer, { ...tiling, hitRects, maxHitBands: REVIEW_PACK_HIT_RECT_MAX })
|
|
1649
|
+
: undefined;
|
|
1650
|
+
if (banded?.coverage.capped) {
|
|
1651
|
+
emit.log(`review-pack: page banded to the ${maxTiles}-tile cap (raise --check-critique-max-tiles for full coverage)`, "warn");
|
|
1652
|
+
}
|
|
1653
|
+
if (banded && banded.hitBands > 0) {
|
|
1654
|
+
emit.log(`review-pack: ${banded.hitBands} band(s) below the tile cap captured for ${hitRects.length} gate-hit rect(s)`, "info");
|
|
1655
|
+
}
|
|
1656
|
+
// Fidelity: prove the full-page screenshot against scrolled viewport
|
|
1657
|
+
// renders while the browser is still open; on disagreement the bands are
|
|
1658
|
+
// re-cut from scrolled captures (see reconcileCaptureFidelity).
|
|
1659
|
+
let bandTiles = banded?.tiles ?? [];
|
|
1660
|
+
let captureFidelity;
|
|
1661
|
+
const fidelityWarnings = [];
|
|
1662
|
+
if (bandTiles.length > 0) {
|
|
1663
|
+
const reconciled = await reconcileCaptureFidelity(browser, bandTiles);
|
|
1664
|
+
bandTiles = reconciled.tiles;
|
|
1665
|
+
captureFidelity = reconciled.fidelity;
|
|
1666
|
+
fidelityWarnings.push(...reconciled.warnings);
|
|
1667
|
+
for (const warning of reconciled.warnings)
|
|
1668
|
+
emit.log(warning, "warn");
|
|
1669
|
+
emit.log(`review-pack: capture fidelity ${captureFidelity.source} ` +
|
|
1670
|
+
`(${captureFidelity.probed.length} band(s) probed, ${captureFidelity.mismatched.length} mismatched)`, "info");
|
|
1671
|
+
}
|
|
1672
|
+
const scopeTiles = [];
|
|
1673
|
+
for (const selector of opts.reviewPackScope ?? []) {
|
|
1674
|
+
const elementRects = await browser.elementTiles(selector);
|
|
1675
|
+
const scoped = tilesFromFullPage(buffer, { ...tiling, elementRects });
|
|
1676
|
+
if (elementRects.length > maxTiles) {
|
|
1677
|
+
emit.log(`review-pack: ${elementRects.length} elements matched ${selector}, capped to ${maxTiles} tiles`, "warn");
|
|
1678
|
+
}
|
|
1679
|
+
scopeTiles.push({ selector, tiles: scoped.tiles, coverage: scoped.coverage });
|
|
1680
|
+
}
|
|
1681
|
+
// Coverage: the bands when present, else the scopes folded together
|
|
1682
|
+
// (heights worst-case, band counts add, capped sticky).
|
|
1683
|
+
let coverage = banded?.coverage;
|
|
1684
|
+
if (!coverage) {
|
|
1685
|
+
for (const scope of scopeTiles) {
|
|
1686
|
+
coverage = coverage
|
|
1687
|
+
? {
|
|
1688
|
+
page_height_px: Math.max(coverage.page_height_px, scope.coverage.page_height_px),
|
|
1689
|
+
reviewed_height_px: Math.min(coverage.reviewed_height_px, scope.coverage.reviewed_height_px),
|
|
1690
|
+
bands_total: coverage.bands_total + scope.coverage.bands_total,
|
|
1691
|
+
bands_reviewed: coverage.bands_reviewed + scope.coverage.bands_reviewed,
|
|
1692
|
+
capped: coverage.capped || scope.coverage.capped,
|
|
1693
|
+
}
|
|
1694
|
+
: scope.coverage;
|
|
1695
|
+
}
|
|
1696
|
+
}
|
|
1697
|
+
const page = await browser.currentPage.evaluate(() => ({
|
|
1698
|
+
width: Math.max(document.documentElement.scrollWidth, document.body?.scrollWidth ?? 0),
|
|
1699
|
+
height: Math.max(document.documentElement.scrollHeight, document.body?.scrollHeight ?? 0),
|
|
1700
|
+
}));
|
|
1701
|
+
const capture = qaCapture ?? (await captureQaState(browser, targetArg, navResult.url, opts));
|
|
1702
|
+
const record = writePackContext(packDir, {
|
|
1703
|
+
context: {
|
|
1704
|
+
...capture.context,
|
|
1705
|
+
...(opts.reviewPackContext ? { id: opts.reviewPackContext } : {}),
|
|
1706
|
+
},
|
|
1707
|
+
url: navResult.url,
|
|
1708
|
+
title: navResult.title,
|
|
1709
|
+
fullPage: buffer,
|
|
1710
|
+
pageWidth: page.width,
|
|
1711
|
+
pageHeight: page.height,
|
|
1712
|
+
tiles: bandTiles,
|
|
1713
|
+
coverage: coverage ?? {
|
|
1714
|
+
page_height_px: page.height,
|
|
1715
|
+
reviewed_height_px: 0,
|
|
1716
|
+
bands_total: 0,
|
|
1717
|
+
bands_reviewed: 0,
|
|
1718
|
+
capped: false,
|
|
1719
|
+
},
|
|
1720
|
+
scopeTiles: scopeTiles.map(({ selector, tiles }) => ({ selector, tiles })),
|
|
1721
|
+
signature: capture.signature,
|
|
1722
|
+
domHtml: capture.domHtml,
|
|
1723
|
+
...(captureFidelity ? { captureFidelity } : {}),
|
|
1724
|
+
hitBands: banded?.hitBands ?? 0,
|
|
1725
|
+
});
|
|
1726
|
+
emit.log(`review-pack: ${record.id} → ${record.tiles.length} tile(s)` +
|
|
1727
|
+
(record.scopes.length > 0
|
|
1728
|
+
? ` (${record.scopes.map((s) => `${s.selector}: ${s.tiles}`).join(", ")})`
|
|
1729
|
+
: "") +
|
|
1730
|
+
` written to ${packDir}`, "info");
|
|
1731
|
+
return {
|
|
1732
|
+
dir: packDir,
|
|
1733
|
+
context_id: record.id,
|
|
1734
|
+
tiles: record.tiles.length,
|
|
1735
|
+
scopes: record.scopes,
|
|
1736
|
+
coverage: record.coverage,
|
|
1737
|
+
files: record.files,
|
|
1738
|
+
...(record.hit_bands !== undefined ? { hit_bands: record.hit_bands } : {}),
|
|
1739
|
+
...(record.capture_fidelity ? { capture_fidelity: record.capture_fidelity } : {}),
|
|
1740
|
+
...(fidelityWarnings.length > 0 ? { warnings: fidelityWarnings } : {}),
|
|
1741
|
+
};
|
|
1742
|
+
}
|
|
1743
|
+
async function captureCritiqueTiles(browser, opts, tileBudgetPx) {
|
|
1744
|
+
const { maxTiles, band, overlap } = critiqueTilingKnobs(opts, tileBudgetPx);
|
|
1436
1745
|
// Capture the whole page ONCE, then crop tiles from the pixels. Playwright's
|
|
1437
1746
|
// clip is viewport-relative, so a below-fold band clip throws ("clipped area
|
|
1438
1747
|
// outside the resulting image") — cropping the full-page image sidesteps that
|
|
@@ -1451,7 +1760,7 @@ async function captureCritiqueTiles(browser, opts, tileBudgetPx) {
|
|
|
1451
1760
|
catch {
|
|
1452
1761
|
atoms = undefined;
|
|
1453
1762
|
}
|
|
1454
|
-
const tiles = tilesFromFullPage(buffer, {
|
|
1763
|
+
const { tiles, coverage } = tilesFromFullPage(buffer, {
|
|
1455
1764
|
elementRects,
|
|
1456
1765
|
bandHeight: band,
|
|
1457
1766
|
overlap,
|
|
@@ -1464,7 +1773,7 @@ async function captureCritiqueTiles(browser, opts, tileBudgetPx) {
|
|
|
1464
1773
|
else if (!elementRects && tiles.length >= maxTiles) {
|
|
1465
1774
|
emit.log(`check-critique: page banded to the ${maxTiles}-tile cap (raise --check-critique-max-tiles for full coverage)`, "warn");
|
|
1466
1775
|
}
|
|
1467
|
-
return { tiles, fullPage: buffer };
|
|
1776
|
+
return { tiles, fullPage: buffer, coverage };
|
|
1468
1777
|
}
|
|
1469
1778
|
function logCritiqueSummary(critique) {
|
|
1470
1779
|
if (critique.outcome === "skipped") {
|
|
@@ -1549,6 +1858,36 @@ async function waitForTerminalEnter(signal) {
|
|
|
1549
1858
|
signal.addEventListener("abort", onAbort, { once: true });
|
|
1550
1859
|
});
|
|
1551
1860
|
}
|
|
1861
|
+
/** Upper bound on --review-pack-hit-rect values (and on hit bands cut past the cap). */
|
|
1862
|
+
const REVIEW_PACK_HIT_RECT_MAX = 50;
|
|
1863
|
+
/** `x,y,w,h` integer rectangles from repeated --review-pack-hit-rect flags. */
|
|
1864
|
+
function parseReviewPackHitRects(specs) {
|
|
1865
|
+
const list = specs ?? [];
|
|
1866
|
+
if (list.length > REVIEW_PACK_HIT_RECT_MAX) {
|
|
1867
|
+
throw new Error(`--review-pack-hit-rect accepts at most ${REVIEW_PACK_HIT_RECT_MAX} rectangles (got ${list.length}).`);
|
|
1868
|
+
}
|
|
1869
|
+
return list.map((spec) => {
|
|
1870
|
+
const parts = spec.split(",").map((part) => part.trim());
|
|
1871
|
+
const nums = parts.map((part) => (/^-?\d+$/.test(part) ? Number.parseInt(part, 10) : NaN));
|
|
1872
|
+
if (parts.length !== 4 || nums.some((n) => !Number.isFinite(n))) {
|
|
1873
|
+
throw new Error(`--review-pack-hit-rect must be four integers x,y,w,h in document px (got: ${spec}).`);
|
|
1874
|
+
}
|
|
1875
|
+
const [x, y, width, height] = nums;
|
|
1876
|
+
if (width < 0 || height < 0) {
|
|
1877
|
+
throw new Error(`--review-pack-hit-rect width and height must not be negative (got: ${spec}).`);
|
|
1878
|
+
}
|
|
1879
|
+
return { x, y, width, height };
|
|
1880
|
+
});
|
|
1881
|
+
}
|
|
1882
|
+
function parseDeviceScaleFactor(spec) {
|
|
1883
|
+
if (spec === undefined)
|
|
1884
|
+
return undefined;
|
|
1885
|
+
const value = Number.parseFloat(spec);
|
|
1886
|
+
if (!Number.isFinite(value) || value < 1 || value > 4) {
|
|
1887
|
+
throw new Error(`--device-scale-factor must be a number between 1 and 4 (got: ${spec})`);
|
|
1888
|
+
}
|
|
1889
|
+
return value;
|
|
1890
|
+
}
|
|
1552
1891
|
function parseViewport(spec) {
|
|
1553
1892
|
const preset = VIEWPORT_PRESETS[spec.toLowerCase()];
|
|
1554
1893
|
if (preset)
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { Command } from "commander";
|
|
2
|
+
import type { EmitContext, HarneryProgramContext } from "../commander.js";
|
|
3
|
+
export declare function registerDiagnosticsCommand(program: Command, emit: EmitContext, context?: HarneryProgramContext): void;
|
|
4
|
+
//# sourceMappingURL=diagnostics.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"diagnostics.d.ts","sourceRoot":"","sources":["../../src/commands/diagnostics.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,WAAW,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAyB1E,wBAAgB,0BAA0B,CACxC,OAAO,EAAE,OAAO,EAChB,IAAI,EAAE,WAAW,EACjB,OAAO,CAAC,EAAE,qBAAqB,GAC9B,IAAI,CAiKN"}
|