harnery 0.32.0 → 0.34.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/README.md +2 -0
- package/dist/commander.d.ts.map +1 -1
- package/dist/commander.js +7 -1
- package/dist/commands/adapter.js +2 -2
- package/dist/commands/agents.d.ts +144 -1
- package/dist/commands/agents.d.ts.map +1 -1
- package/dist/commands/agents.js +1408 -565
- package/dist/commands/artifacts.d.ts.map +1 -1
- package/dist/commands/artifacts.js +7 -6
- package/dist/commands/backup.d.ts +1 -1
- package/dist/commands/backup.d.ts.map +1 -1
- package/dist/commands/backup.js +29 -16
- package/dist/commands/browse.d.ts.map +1 -1
- package/dist/commands/browse.js +234 -12
- package/dist/commands/checkpoint.d.ts.map +1 -1
- package/dist/commands/checkpoint.js +3 -2
- package/dist/commands/claude-desktop.d.ts +2 -2
- package/dist/commands/claude-desktop.d.ts.map +1 -1
- package/dist/commands/claude-desktop.js +2 -2
- package/dist/commands/context.js +1 -6
- package/dist/commands/decision.d.ts.map +1 -1
- package/dist/commands/decision.js +22 -38
- package/dist/commands/docs.d.ts.map +1 -1
- package/dist/commands/docs.js +91 -28
- package/dist/commands/doctor.d.ts +4 -2
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +55 -7
- package/dist/commands/events.d.ts +5 -0
- package/dist/commands/events.d.ts.map +1 -0
- package/dist/commands/events.js +189 -0
- package/dist/commands/governor.d.ts.map +1 -1
- package/dist/commands/governor.js +13 -4
- package/dist/commands/init.d.ts +10 -2
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +233 -51
- package/dist/commands/instructions.d.ts +4 -0
- package/dist/commands/instructions.d.ts.map +1 -0
- package/dist/commands/instructions.js +100 -0
- package/dist/commands/journal.d.ts.map +1 -1
- package/dist/commands/journal.js +4 -13
- package/dist/commands/ledger-v3.d.ts +5 -0
- package/dist/commands/ledger-v3.d.ts.map +1 -0
- package/dist/commands/ledger-v3.js +91 -0
- package/dist/commands/presence.d.ts.map +1 -1
- package/dist/commands/presence.js +15 -6
- package/dist/commands/sync.d.ts +1 -1
- package/dist/commands/sync.js +1 -1
- package/dist/commands/web.d.ts +6 -0
- package/dist/commands/web.d.ts.map +1 -1
- package/dist/commands/web.js +67 -7
- package/dist/commands/work.d.ts.map +1 -1
- package/dist/commands/work.js +22 -5
- package/dist/core/adapter.d.ts +5 -0
- package/dist/core/adapter.d.ts.map +1 -0
- package/dist/core/adapter.js +1 -0
- package/dist/core/adapters/attest.d.ts +5 -2
- package/dist/core/adapters/attest.d.ts.map +1 -1
- package/dist/core/adapters/attest.js +8 -3
- package/dist/core/adapters/profiles.d.ts +6 -6
- package/dist/core/adapters/profiles.d.ts.map +1 -1
- package/dist/core/adapters/profiles.js +4 -4
- package/dist/core/agents/canonical-emit.d.ts +17 -15
- package/dist/core/agents/canonical-emit.d.ts.map +1 -1
- package/dist/core/agents/canonical-emit.js +27 -25
- package/dist/core/agents/cli.js +212 -235
- package/dist/core/agents/codex-archive-v3.d.ts +14 -0
- package/dist/core/agents/codex-archive-v3.d.ts.map +1 -0
- package/dist/core/agents/codex-archive-v3.js +110 -0
- package/dist/core/agents/coord-client.d.ts +19 -70
- package/dist/core/agents/coord-client.d.ts.map +1 -1
- package/dist/core/agents/coord-client.js +65 -155
- package/dist/core/agents/finalization.d.ts +4 -1
- package/dist/core/agents/finalization.d.ts.map +1 -1
- package/dist/core/agents/finalization.js +89 -32
- package/dist/core/agents/index.d.ts +5 -5
- package/dist/core/agents/index.d.ts.map +1 -1
- package/dist/core/agents/index.js +5 -5
- package/dist/core/agents/live-authority-v3.d.ts +59 -0
- package/dist/core/agents/live-authority-v3.d.ts.map +1 -0
- package/dist/core/agents/live-authority-v3.js +233 -0
- package/dist/core/agents/live-lifecycle-v3.d.ts +20 -0
- package/dist/core/agents/live-lifecycle-v3.d.ts.map +1 -0
- package/dist/core/agents/live-lifecycle-v3.js +91 -0
- package/dist/core/agents/live-observation-v3.d.ts +25 -0
- package/dist/core/agents/live-observation-v3.d.ts.map +1 -0
- package/dist/core/agents/live-observation-v3.js +157 -0
- package/dist/core/agents/render/prompt-context.d.ts +10 -4
- package/dist/core/agents/render/prompt-context.d.ts.map +1 -1
- package/dist/core/agents/render/prompt-context.js +61 -55
- package/dist/core/agents/render/session-context.d.ts +1 -1
- package/dist/core/agents/render/session-context.d.ts.map +1 -1
- package/dist/core/agents/render/session-context.js +15 -33
- package/dist/core/agents/rules/claim-conflict.d.ts +3 -3
- package/dist/core/agents/rules/claim-conflict.d.ts.map +1 -1
- package/dist/core/agents/rules/claim-conflict.js +79 -127
- package/dist/core/agents/rules/commit-conflict.d.ts.map +1 -1
- package/dist/core/agents/rules/commit-conflict.js +3 -18
- package/dist/core/agents/rules/stop-hook.d.ts +9 -50
- package/dist/core/agents/rules/stop-hook.d.ts.map +1 -1
- package/dist/core/agents/rules/stop-hook.js +193 -236
- package/dist/core/agents/session-events.d.ts +5 -7
- package/dist/core/agents/session-events.d.ts.map +1 -1
- package/dist/core/agents/session-events.js +127 -95
- package/dist/core/agents/session-finalization-state-v3.d.ts +30 -0
- package/dist/core/agents/session-finalization-state-v3.d.ts.map +1 -0
- package/dist/core/agents/session-finalization-state-v3.js +40 -0
- package/dist/core/agents/session-finalizer-v3.d.ts +107 -0
- package/dist/core/agents/session-finalizer-v3.d.ts.map +1 -0
- package/dist/core/agents/session-finalizer-v3.js +734 -0
- package/dist/core/agents/session-name-display.d.ts +40 -0
- package/dist/core/agents/session-name-display.d.ts.map +1 -0
- package/dist/core/agents/session-name-display.js +119 -0
- package/dist/core/agents/state/activity-log.d.ts +3 -3
- package/dist/core/agents/state/activity-log.d.ts.map +1 -1
- package/dist/core/agents/state/activity-log.js +7 -16
- package/dist/core/agents/state/heartbeat-writer.d.ts +29 -46
- package/dist/core/agents/state/heartbeat-writer.d.ts.map +1 -1
- package/dist/core/agents/state/heartbeat-writer.js +59 -199
- package/dist/core/agents/state/live-coordination-view.d.ts +31 -0
- package/dist/core/agents/state/live-coordination-view.d.ts.map +1 -0
- package/dist/core/agents/state/live-coordination-view.js +209 -0
- package/dist/core/agents/state/session-state.d.ts +45 -0
- package/dist/core/agents/state/session-state.d.ts.map +1 -0
- package/dist/core/agents/state/session-state.js +90 -0
- package/dist/core/agents/state/stale-sweep.d.ts +1 -1
- package/dist/core/agents/state/stale-sweep.d.ts.map +1 -1
- package/dist/core/agents/state/stale-sweep.js +26 -15
- package/dist/core/artifacts/index.d.ts.map +1 -1
- package/dist/core/artifacts/index.js +5 -7
- package/dist/core/config.d.ts +33 -2
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +98 -2
- package/dist/core/context/index.d.ts +1 -4
- package/dist/core/context/index.d.ts.map +1 -1
- package/dist/core/context/index.js +9 -9
- package/dist/core/events/span-parent.d.ts +15 -0
- package/dist/core/events/span-parent.d.ts.map +1 -0
- package/dist/core/events/span-parent.js +34 -0
- package/dist/core/events/v3/advance.d.ts +14 -0
- package/dist/core/events/v3/advance.d.ts.map +1 -0
- package/dist/core/events/v3/advance.js +134 -0
- package/dist/core/events/v3/authority-outbox.d.ts +78 -0
- package/dist/core/events/v3/authority-outbox.d.ts.map +1 -0
- package/dist/core/events/v3/authority-outbox.js +399 -0
- package/dist/core/events/v3/base-builder.d.ts +20 -0
- package/dist/core/events/v3/base-builder.d.ts.map +1 -0
- package/dist/core/events/v3/base-builder.js +29 -0
- package/dist/core/events/v3/base-contract.d.ts +7576 -0
- package/dist/core/events/v3/base-contract.d.ts.map +1 -0
- package/dist/core/events/v3/base-contract.js +647 -0
- package/dist/core/events/v3/bootstrap.d.ts +34 -0
- package/dist/core/events/v3/bootstrap.d.ts.map +1 -0
- package/dist/core/events/v3/bootstrap.js +175 -0
- package/dist/core/events/v3/builder.d.ts +20 -0
- package/dist/core/events/v3/builder.d.ts.map +1 -0
- package/dist/core/events/v3/builder.js +28 -0
- package/dist/core/events/v3/canonical.d.ts +31 -0
- package/dist/core/events/v3/canonical.d.ts.map +1 -0
- package/dist/core/events/v3/canonical.js +100 -0
- package/dist/core/events/v3/capabilities.d.ts +31 -0
- package/dist/core/events/v3/capabilities.d.ts.map +1 -0
- package/dist/core/events/v3/capabilities.js +107 -0
- package/dist/core/events/v3/capability-drift.d.ts +22 -0
- package/dist/core/events/v3/capability-drift.d.ts.map +1 -0
- package/dist/core/events/v3/capability-drift.js +134 -0
- package/dist/core/events/v3/catalog.d.ts +38 -0
- package/dist/core/events/v3/catalog.d.ts.map +1 -0
- package/dist/core/events/v3/catalog.js +135 -0
- package/dist/core/events/v3/clock-order.d.ts +34 -0
- package/dist/core/events/v3/clock-order.d.ts.map +1 -0
- package/dist/core/events/v3/clock-order.js +35 -0
- package/dist/core/events/v3/contract.d.ts +171 -0
- package/dist/core/events/v3/contract.d.ts.map +1 -0
- package/dist/core/events/v3/contract.js +180 -0
- package/dist/core/events/v3/control.d.ts +111 -0
- package/dist/core/events/v3/control.d.ts.map +1 -0
- package/dist/core/events/v3/control.js +419 -0
- package/dist/core/events/v3/coordination-view.d.ts +69 -0
- package/dist/core/events/v3/coordination-view.d.ts.map +1 -0
- package/dist/core/events/v3/coordination-view.js +119 -0
- package/dist/core/events/v3/economics.d.ts +77 -0
- package/dist/core/events/v3/economics.d.ts.map +1 -0
- package/dist/core/events/v3/economics.js +274 -0
- package/dist/core/events/v3/finalization-view.d.ts +26 -0
- package/dist/core/events/v3/finalization-view.d.ts.map +1 -0
- package/dist/core/events/v3/finalization-view.js +37 -0
- package/dist/core/events/v3/fingerprint-keys.d.ts +25 -0
- package/dist/core/events/v3/fingerprint-keys.d.ts.map +1 -0
- package/dist/core/events/v3/fingerprint-keys.js +217 -0
- package/dist/core/events/v3/generated.d.ts +4 -0
- package/dist/core/events/v3/generated.d.ts.map +1 -0
- package/dist/core/events/v3/generated.js +47 -0
- package/dist/core/events/v3/ids.d.ts +9 -0
- package/dist/core/events/v3/ids.d.ts.map +1 -0
- package/dist/core/events/v3/ids.js +25 -0
- package/dist/core/events/v3/index.d.ts +35 -0
- package/dist/core/events/v3/index.d.ts.map +1 -0
- package/dist/core/events/v3/index.js +33 -0
- package/dist/core/events/v3/latency.d.ts +63 -0
- package/dist/core/events/v3/latency.d.ts.map +1 -0
- package/dist/core/events/v3/latency.js +412 -0
- package/dist/core/events/v3/live-feed.d.ts +45 -0
- package/dist/core/events/v3/live-feed.d.ts.map +1 -0
- package/dist/core/events/v3/live-feed.js +246 -0
- package/dist/core/events/v3/live-observation.d.ts +35 -0
- package/dist/core/events/v3/live-observation.d.ts.map +1 -0
- package/dist/core/events/v3/live-observation.js +1 -0
- package/dist/core/events/v3/live-routing.d.ts +57 -0
- package/dist/core/events/v3/live-routing.d.ts.map +1 -0
- package/dist/core/events/v3/live-routing.js +107 -0
- package/dist/core/events/v3/producers/command-recorder.d.ts +67 -0
- package/dist/core/events/v3/producers/command-recorder.d.ts.map +1 -0
- package/dist/core/events/v3/producers/command-recorder.js +477 -0
- package/dist/core/events/v3/producers/command.d.ts +50 -0
- package/dist/core/events/v3/producers/command.d.ts.map +1 -0
- package/dist/core/events/v3/producers/command.js +135 -0
- package/dist/core/events/v3/producers/coordination-recorder.d.ts +53 -0
- package/dist/core/events/v3/producers/coordination-recorder.d.ts.map +1 -0
- package/dist/core/events/v3/producers/coordination-recorder.js +376 -0
- package/dist/core/events/v3/producers/coordination.d.ts +90 -0
- package/dist/core/events/v3/producers/coordination.d.ts.map +1 -0
- package/dist/core/events/v3/producers/coordination.js +229 -0
- package/dist/core/events/v3/producers/hook-base.d.ts +54 -0
- package/dist/core/events/v3/producers/hook-base.d.ts.map +1 -0
- package/dist/core/events/v3/producers/hook-base.js +424 -0
- package/dist/core/events/v3/producers/hook.d.ts +29 -0
- package/dist/core/events/v3/producers/hook.d.ts.map +1 -0
- package/dist/core/events/v3/producers/hook.js +71 -0
- package/dist/core/events/v3/producers/intake.d.ts +65 -0
- package/dist/core/events/v3/producers/intake.d.ts.map +1 -0
- package/dist/core/events/v3/producers/intake.js +244 -0
- package/dist/core/events/v3/producers/recorder.d.ts +240 -0
- package/dist/core/events/v3/producers/recorder.d.ts.map +1 -0
- package/dist/core/events/v3/producers/recorder.js +2176 -0
- package/dist/core/events/v3/projection.d.ts +101 -0
- package/dist/core/events/v3/projection.d.ts.map +1 -0
- package/dist/core/events/v3/projection.js +479 -0
- package/dist/core/events/v3/reader.d.ts +75 -0
- package/dist/core/events/v3/reader.d.ts.map +1 -0
- package/dist/core/events/v3/reader.js +778 -0
- package/dist/core/events/v3/recovery-record.d.ts +47 -0
- package/dist/core/events/v3/recovery-record.d.ts.map +1 -0
- package/dist/core/events/v3/recovery-record.js +167 -0
- package/dist/core/events/v3/recovery.d.ts +22 -0
- package/dist/core/events/v3/recovery.d.ts.map +1 -0
- package/dist/core/events/v3/recovery.js +227 -0
- package/dist/core/events/v3/span-state.d.ts +52 -0
- package/dist/core/events/v3/span-state.d.ts.map +1 -0
- package/dist/core/events/v3/span-state.js +142 -0
- package/dist/core/events/v3/targets.d.ts +23 -0
- package/dist/core/events/v3/targets.d.ts.map +1 -0
- package/dist/core/events/v3/targets.js +134 -0
- package/dist/core/events/v3/turn-telemetry.d.ts +41 -0
- package/dist/core/events/v3/turn-telemetry.d.ts.map +1 -0
- package/dist/core/events/v3/turn-telemetry.js +168 -0
- package/dist/core/events/v3/validate.d.ts +9 -0
- package/dist/core/events/v3/validate.d.ts.map +1 -0
- package/dist/core/events/v3/validate.js +281 -0
- package/dist/core/events/v3/writer.d.ts +56 -0
- package/dist/core/events/v3/writer.d.ts.map +1 -0
- package/dist/core/events/v3/writer.js +335 -0
- package/dist/core/governor/index.d.ts +1 -1
- package/dist/core/governor/index.d.ts.map +1 -1
- package/dist/core/governor/index.js +1 -1
- package/dist/core/governor/plan-api.d.ts +2 -1
- package/dist/core/governor/plan-api.d.ts.map +1 -1
- package/dist/core/governor/plan-api.js +6 -3
- package/dist/core/governor/read.d.ts +1 -1
- package/dist/core/governor/read.d.ts.map +1 -1
- package/dist/core/governor/read.js +1 -1
- package/dist/core/governor/state.d.ts +9 -0
- package/dist/core/governor/state.d.ts.map +1 -1
- package/dist/core/governor/state.js +40 -3
- package/dist/core/guard/config.d.ts +5 -0
- package/dist/core/guard/config.d.ts.map +1 -0
- package/dist/core/guard/config.js +157 -0
- package/dist/core/guard/coordinator.d.ts +14 -0
- package/dist/core/guard/coordinator.d.ts.map +1 -0
- package/dist/core/guard/coordinator.js +173 -0
- package/dist/core/guard/evaluator.d.ts +4 -0
- package/dist/core/guard/evaluator.d.ts.map +1 -0
- package/dist/core/guard/evaluator.js +242 -0
- package/dist/core/guard/evidence-v3.d.ts +15 -0
- package/dist/core/guard/evidence-v3.d.ts.map +1 -0
- package/dist/core/guard/evidence-v3.js +171 -0
- package/dist/core/guard/index.d.ts +10 -0
- package/dist/core/guard/index.d.ts.map +1 -0
- package/dist/core/guard/index.js +6 -0
- package/dist/core/guard/live-source-v3.d.ts +40 -0
- package/dist/core/guard/live-source-v3.d.ts.map +1 -0
- package/dist/core/guard/live-source-v3.js +128 -0
- package/dist/core/guard/storage.d.ts +27 -0
- package/dist/core/guard/storage.d.ts.map +1 -0
- package/dist/core/guard/storage.js +140 -0
- package/dist/core/guard/types.d.ts +119 -0
- package/dist/core/guard/types.d.ts.map +1 -0
- package/dist/core/guard/types.js +1 -0
- package/dist/core/hooks/adapter/detect.d.ts +18 -1
- package/dist/core/hooks/adapter/detect.d.ts.map +1 -1
- package/dist/core/hooks/adapter/detect.js +29 -0
- package/dist/core/hooks/adapter/events.d.ts +7 -4
- package/dist/core/hooks/adapter/events.d.ts.map +1 -1
- package/dist/core/hooks/adapter/events.js +13 -4
- package/dist/core/hooks/adapter/output.d.ts +2 -2
- package/dist/core/hooks/adapter/output.d.ts.map +1 -1
- package/dist/core/hooks/adapter/parse.d.ts +7 -4
- package/dist/core/hooks/adapter/parse.d.ts.map +1 -1
- package/dist/core/hooks/adapter/parse.js +64 -24
- package/dist/core/hooks/adapter/wiring.d.ts +30 -4
- package/dist/core/hooks/adapter/wiring.d.ts.map +1 -1
- package/dist/core/hooks/adapter/wiring.js +89 -11
- package/dist/core/hooks/cli.d.ts +1 -1
- package/dist/core/hooks/cli.js +345 -400
- package/dist/core/hooks/codex-authorization.d.ts +30 -0
- package/dist/core/hooks/codex-authorization.d.ts.map +1 -0
- package/dist/core/hooks/codex-authorization.js +199 -0
- package/dist/core/hooks/effects/image-capture.d.ts +20 -34
- package/dist/core/hooks/effects/image-capture.d.ts.map +1 -1
- package/dist/core/hooks/effects/image-capture.js +212 -221
- package/dist/core/hooks/effects/index.d.ts +1 -9
- package/dist/core/hooks/effects/index.d.ts.map +1 -1
- package/dist/core/hooks/effects/index.js +1 -24
- package/dist/core/hooks/events/input-hash.d.ts +7 -0
- package/dist/core/hooks/events/input-hash.d.ts.map +1 -0
- package/dist/core/hooks/events/input-hash.js +43 -0
- package/dist/core/hooks/index.d.ts +4 -3
- package/dist/core/hooks/index.d.ts.map +1 -1
- package/dist/core/hooks/index.js +2 -3
- package/dist/core/hooks/resolve/coord-root.d.ts +1 -1
- package/dist/core/hooks/resolve/coord-root.js +1 -1
- package/dist/core/hooks/resolve/owner.d.ts +16 -15
- package/dist/core/hooks/resolve/owner.d.ts.map +1 -1
- package/dist/core/hooks/resolve/owner.js +51 -42
- package/dist/core/hooks/resolve/transcript.d.ts +33 -3
- package/dist/core/hooks/resolve/transcript.d.ts.map +1 -1
- package/dist/core/hooks/resolve/transcript.js +155 -14
- package/dist/core/hooks/session-name-presence.d.ts +9 -10
- package/dist/core/hooks/session-name-presence.d.ts.map +1 -1
- package/dist/core/hooks/session-name-presence.js +15 -15
- package/dist/core/hooks/unsafe-cross-shell.d.ts +1 -1
- package/dist/core/hooks/unsafe-cross-shell.d.ts.map +1 -1
- package/dist/core/journal/index.d.ts +2 -2
- package/dist/core/journal/index.d.ts.map +1 -1
- package/dist/core/journal/index.js +9 -22
- package/dist/core/presence/blob.d.ts +8 -3
- package/dist/core/presence/blob.d.ts.map +1 -1
- package/dist/core/presence/blob.js +54 -41
- package/dist/core/presence/codec-digest.d.ts +29 -0
- package/dist/core/presence/codec-digest.d.ts.map +1 -0
- package/dist/core/presence/codec-digest.js +170 -0
- package/dist/core/presence/index.js +15 -1
- package/dist/core/presence/relay-client.d.ts +1 -1
- package/dist/core/presence/relay-client.d.ts.map +1 -1
- package/dist/core/presence/relay-client.js +5 -5
- package/dist/core/work/index.d.ts +1 -1
- package/dist/core/work/index.d.ts.map +1 -1
- package/dist/core/work/index.js +1 -1
- package/dist/core/work/read.d.ts +1 -1
- package/dist/core/work/read.d.ts.map +1 -1
- package/dist/core/work/read.js +1 -1
- package/dist/core/work/runner.d.ts.map +1 -1
- package/dist/core/work/runner.js +6 -0
- package/dist/core/work/state.d.ts +9 -0
- package/dist/core/work/state.d.ts.map +1 -1
- package/dist/core/work/state.js +17 -3
- package/dist/core/workflow/engine.js +24 -25
- package/dist/core/workflow/live-session-v3.d.ts +19 -0
- package/dist/core/workflow/live-session-v3.d.ts.map +1 -0
- package/dist/core/workflow/live-session-v3.js +86 -0
- package/dist/core/workflow/scope-id.d.ts +3 -0
- package/dist/core/workflow/scope-id.d.ts.map +1 -0
- package/dist/core/workflow/scope-id.js +5 -0
- package/dist/core/workflow/workspaces/leases.d.ts +4 -0
- package/dist/core/workflow/workspaces/leases.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/leases.js +53 -19
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/lib/browser/client.d.ts +41 -0
- package/dist/lib/browser/client.d.ts.map +1 -1
- package/dist/lib/browser/client.js +203 -0
- package/dist/lib/browser/critique.d.ts +28 -2
- package/dist/lib/browser/critique.d.ts.map +1 -1
- package/dist/lib/browser/critique.js +74 -18
- package/dist/lib/browser/geometry.d.ts +1 -1
- package/dist/lib/browser/geometry.d.ts.map +1 -1
- package/dist/lib/browser/geometry.js +130 -6
- package/dist/lib/browser/index.d.ts +3 -0
- package/dist/lib/browser/index.d.ts.map +1 -1
- package/dist/lib/browser/index.js +3 -0
- package/dist/lib/browser/qa-plan.d.ts +174 -0
- package/dist/lib/browser/qa-plan.d.ts.map +1 -0
- package/dist/lib/browser/qa-plan.js +321 -0
- package/dist/lib/browser/qa-reuse.d.ts +99 -0
- package/dist/lib/browser/qa-reuse.d.ts.map +1 -0
- package/dist/lib/browser/qa-reuse.js +222 -0
- package/dist/lib/browser/qa-snapshot.d.ts +64 -0
- package/dist/lib/browser/qa-snapshot.d.ts.map +1 -0
- package/dist/lib/browser/qa-snapshot.js +171 -0
- package/dist/lib/browser/runts.d.ts.map +1 -1
- package/dist/lib/browser/runts.js +22 -1
- package/dist/lib/browser/tiling.d.ts +71 -0
- package/dist/lib/browser/tiling.d.ts.map +1 -0
- package/dist/lib/browser/tiling.js +118 -0
- package/dist/lib/context/index.d.ts +0 -3
- package/dist/lib/context/index.d.ts.map +1 -1
- package/dist/lib/context/index.js +20 -42
- package/dist/lib/council/index.d.ts +2 -2
- package/dist/lib/council/index.d.ts.map +1 -1
- package/dist/lib/council/index.js +9 -20
- package/dist/lib/docs-frontmatter.d.ts +1 -6
- package/dist/lib/docs-frontmatter.d.ts.map +1 -1
- package/dist/lib/docs-frontmatter.js +12 -78
- package/dist/lib/docs-index.d.ts +9 -0
- package/dist/lib/docs-index.d.ts.map +1 -1
- package/dist/lib/docs-index.js +32 -3
- package/dist/lib/docs-lint.d.ts.map +1 -1
- package/dist/lib/docs-lint.js +14 -30
- package/dist/lib/docs-metadata-audit.d.ts +19 -0
- package/dist/lib/docs-metadata-audit.d.ts.map +1 -0
- package/dist/lib/docs-metadata-audit.js +132 -0
- package/dist/lib/docs-metadata-managed.d.ts +23 -0
- package/dist/lib/docs-metadata-managed.d.ts.map +1 -0
- package/dist/lib/docs-metadata-managed.js +78 -0
- package/dist/lib/docs-metadata-sync.d.ts +20 -0
- package/dist/lib/docs-metadata-sync.d.ts.map +1 -0
- package/dist/lib/docs-metadata-sync.js +222 -0
- package/dist/lib/docs-metadata-v2-schema.d.ts +273 -0
- package/dist/lib/docs-metadata-v2-schema.d.ts.map +1 -0
- package/dist/lib/docs-metadata-v2-schema.js +134 -0
- package/dist/lib/docs-metadata-v2.d.ts +21 -0
- package/dist/lib/docs-metadata-v2.d.ts.map +1 -0
- package/dist/lib/docs-metadata-v2.js +424 -0
- package/dist/lib/docs-new.d.ts +11 -0
- package/dist/lib/docs-new.d.ts.map +1 -0
- package/dist/lib/docs-new.js +52 -0
- package/dist/lib/docs-sweep.d.ts.map +1 -1
- package/dist/lib/docs-sweep.js +45 -29
- package/dist/lib/headless/index.d.ts +98 -0
- package/dist/lib/headless/index.d.ts.map +1 -0
- package/dist/lib/headless/index.js +211 -0
- package/dist/lib/identities/assume.d.ts +4 -4
- package/dist/lib/identities/assume.d.ts.map +1 -1
- package/dist/lib/identities/assume.js +52 -68
- package/dist/lib/instructions/apply.d.ts +5 -4
- package/dist/lib/instructions/apply.d.ts.map +1 -1
- package/dist/lib/instructions/apply.js +32 -20
- package/dist/lib/instructions/bundle.d.ts +33 -0
- package/dist/lib/instructions/bundle.d.ts.map +1 -0
- package/dist/lib/instructions/bundle.js +149 -0
- package/dist/lib/instructions/splice.d.ts +0 -1
- package/dist/lib/instructions/splice.d.ts.map +1 -1
- package/dist/lib/instructions/splice.js +22 -6
- package/dist/lib/instructions/templates.d.ts +7 -5
- package/dist/lib/instructions/templates.d.ts.map +1 -1
- package/dist/lib/instructions/templates.js +79 -9
- package/package.json +27 -6
- package/schemas/config.schema.json +120 -2
- package/schemas/docs-metadata-v2.schema.json +441 -0
- package/schemas/event-v3.schema.json +24472 -0
- package/src/commander.ts +7 -1
- package/src/commands/adapter.ts +2 -2
- package/src/commands/agents.ts +1737 -570
- package/src/commands/artifacts.ts +7 -6
- package/src/commands/backup.ts +29 -21
- package/src/commands/browse.ts +349 -8
- package/src/commands/checkpoint.ts +4 -3
- package/src/commands/claude-desktop.ts +7 -5
- package/src/commands/context.ts +1 -6
- package/src/commands/decision.ts +27 -44
- package/src/commands/docs.ts +100 -33
- package/src/commands/doctor.ts +73 -8
- package/src/commands/events.ts +256 -0
- package/src/commands/governor.ts +18 -4
- package/src/commands/init.ts +264 -57
- package/src/commands/instructions.ts +129 -0
- package/src/commands/journal.ts +4 -13
- package/src/commands/ledger-v3.ts +111 -0
- package/src/commands/presence.ts +18 -11
- package/src/commands/sync.ts +1 -1
- package/src/commands/web.ts +67 -8
- package/src/commands/work.ts +28 -5
- package/src/core/adapter.ts +5 -0
- package/src/core/adapters/attest.ts +8 -3
- package/src/core/adapters/profiles.ts +4 -6
- package/src/core/agents/canonical-emit.ts +39 -28
- package/src/core/agents/cli.ts +242 -263
- package/src/core/agents/codex-archive-v3.ts +138 -0
- package/src/core/agents/coord-client.ts +75 -177
- package/src/core/agents/finalization.ts +120 -38
- package/src/core/agents/index.ts +5 -5
- package/src/core/agents/live-authority-v3.ts +375 -0
- package/src/core/agents/live-lifecycle-v3.ts +140 -0
- package/src/core/agents/live-observation-v3.ts +216 -0
- package/src/core/agents/render/prompt-context.ts +89 -55
- package/src/core/agents/render/session-context.ts +23 -32
- package/src/core/agents/rules/claim-conflict.ts +85 -126
- package/src/core/agents/rules/commit-conflict.ts +3 -14
- package/src/core/agents/rules/stop-hook.ts +239 -293
- package/src/core/agents/session-events.ts +137 -104
- package/src/core/agents/session-finalization-state-v3.ts +90 -0
- package/src/core/agents/session-finalizer-v3.ts +953 -0
- package/src/core/agents/session-name-display.ts +149 -0
- package/src/core/agents/state/activity-log.ts +6 -14
- package/src/core/agents/state/heartbeat-writer.ts +98 -242
- package/src/core/agents/state/live-coordination-view.ts +290 -0
- package/src/core/agents/state/session-state.ts +143 -0
- package/src/core/agents/state/stale-sweep.ts +40 -17
- package/src/core/artifacts/index.ts +4 -5
- package/src/core/config.ts +148 -3
- package/src/core/context/index.ts +9 -17
- package/src/core/events/span-parent.ts +46 -0
- package/src/core/events/v3/advance.ts +168 -0
- package/src/core/events/v3/authority-outbox.ts +548 -0
- package/src/core/events/v3/base-builder.ts +60 -0
- package/src/core/events/v3/base-contract.ts +882 -0
- package/src/core/events/v3/bootstrap.ts +226 -0
- package/src/core/events/v3/builder.ts +59 -0
- package/src/core/events/v3/canonical.ts +153 -0
- package/src/core/events/v3/capabilities.ts +171 -0
- package/src/core/events/v3/capability-drift.ts +177 -0
- package/src/core/events/v3/catalog.ts +202 -0
- package/src/core/events/v3/clock-order.ts +41 -0
- package/src/core/events/v3/contract.ts +365 -0
- package/src/core/events/v3/control.ts +576 -0
- package/src/core/events/v3/coordination-view.ts +193 -0
- package/src/core/events/v3/economics.ts +384 -0
- package/src/core/events/v3/finalization-view.ts +67 -0
- package/src/core/events/v3/fingerprint-keys.ts +260 -0
- package/src/core/events/v3/generated.ts +48 -0
- package/src/core/events/v3/ids.ts +33 -0
- package/src/core/events/v3/index.ts +322 -0
- package/src/core/events/v3/latency.ts +551 -0
- package/src/core/events/v3/live-feed.ts +319 -0
- package/src/core/events/v3/live-observation.ts +39 -0
- package/src/core/events/v3/live-routing.ts +135 -0
- package/src/core/events/v3/producers/command-recorder.ts +631 -0
- package/src/core/events/v3/producers/command.ts +201 -0
- package/src/core/events/v3/producers/coordination-recorder.ts +538 -0
- package/src/core/events/v3/producers/coordination.ts +355 -0
- package/src/core/events/v3/producers/hook-base.ts +578 -0
- package/src/core/events/v3/producers/hook.ts +119 -0
- package/src/core/events/v3/producers/intake.ts +319 -0
- package/src/core/events/v3/producers/recorder.ts +2805 -0
- package/src/core/events/v3/projection.ts +653 -0
- package/src/core/events/v3/reader.ts +1127 -0
- package/src/core/events/v3/recovery-record.ts +244 -0
- package/src/core/events/v3/recovery.ts +271 -0
- package/src/core/events/v3/span-state.ts +187 -0
- package/src/core/events/v3/targets.ts +182 -0
- package/src/core/events/v3/turn-telemetry.ts +259 -0
- package/src/core/events/v3/validate.ts +361 -0
- package/src/core/events/v3/writer.ts +419 -0
- package/src/core/governor/index.ts +3 -0
- package/src/core/governor/plan-api.ts +10 -2
- package/src/core/governor/read.ts +3 -0
- package/src/core/governor/state.ts +52 -5
- package/src/core/guard/config.ts +206 -0
- package/src/core/guard/coordinator.ts +227 -0
- package/src/core/guard/evaluator.ts +343 -0
- package/src/core/guard/evidence-v3.ts +227 -0
- package/src/core/guard/index.ts +35 -0
- package/src/core/guard/live-source-v3.ts +204 -0
- package/src/core/guard/storage.ts +188 -0
- package/src/core/guard/types.ts +152 -0
- package/src/core/hooks/adapter/detect.ts +28 -1
- package/src/core/hooks/adapter/events.ts +15 -6
- package/src/core/hooks/adapter/output.ts +7 -2
- package/src/core/hooks/adapter/parse.ts +81 -36
- package/src/core/hooks/adapter/wiring.ts +119 -13
- package/src/core/hooks/cli.ts +398 -422
- package/src/core/hooks/codex-authorization.ts +253 -0
- package/src/core/hooks/effects/image-capture.ts +228 -238
- package/src/core/hooks/effects/index.ts +1 -36
- package/src/core/hooks/events/input-hash.ts +47 -0
- package/src/core/hooks/index.ts +4 -11
- package/src/core/hooks/resolve/coord-root.ts +1 -1
- package/src/core/hooks/resolve/owner.ts +52 -45
- package/src/core/hooks/resolve/transcript.ts +165 -14
- package/src/core/hooks/session-name-presence.ts +14 -15
- package/src/core/hooks/unsafe-cross-shell.ts +1 -1
- package/src/core/journal/index.ts +15 -22
- package/src/core/presence/blob.ts +63 -38
- package/src/core/presence/codec-digest.ts +215 -0
- package/src/core/presence/index.ts +18 -1
- package/src/core/presence/relay-client.ts +5 -5
- package/src/core/work/index.ts +3 -0
- package/src/core/work/read.ts +3 -0
- package/src/core/work/runner.ts +6 -0
- package/src/core/work/state.ts +27 -3
- package/src/core/workflow/engine.ts +24 -24
- package/src/core/workflow/live-session-v3.ts +126 -0
- package/src/core/workflow/scope-id.ts +6 -0
- package/src/core/workflow/workspaces/leases.ts +56 -19
- package/src/index.ts +1 -1
- package/src/lib/browser/client.ts +230 -0
- package/src/lib/browser/critique.ts +104 -27
- package/src/lib/browser/geometry.ts +143 -14
- package/src/lib/browser/index.ts +34 -0
- package/src/lib/browser/qa-plan.ts +501 -0
- package/src/lib/browser/qa-reuse.ts +306 -0
- package/src/lib/browser/qa-snapshot.ts +223 -0
- package/src/lib/browser/runts.ts +27 -2
- package/src/lib/browser/tiling.ts +164 -0
- package/src/lib/context/index.ts +22 -46
- package/src/lib/council/index.ts +9 -21
- package/src/lib/docs-frontmatter.ts +8 -79
- package/src/lib/docs-index.ts +33 -3
- package/src/lib/docs-lint.ts +15 -29
- package/src/lib/docs-metadata-audit.ts +166 -0
- package/src/lib/docs-metadata-managed.ts +83 -0
- package/src/lib/docs-metadata-sync.ts +262 -0
- package/src/lib/docs-metadata-v2-schema.ts +138 -0
- package/src/lib/docs-metadata-v2.ts +646 -0
- package/src/lib/docs-new.ts +63 -0
- package/src/lib/docs-sweep.ts +42 -28
- package/src/lib/headless/index.ts +287 -0
- package/src/lib/identities/assume.ts +67 -86
- package/src/lib/instructions/apply.ts +33 -20
- package/src/lib/instructions/bundle.ts +199 -0
- package/src/lib/instructions/splice.ts +21 -7
- package/src/lib/instructions/templates.ts +85 -13
- package/dist/core/agents/cli-emit.d.ts +0 -27
- package/dist/core/agents/cli-emit.d.ts.map +0 -1
- package/dist/core/agents/cli-emit.js +0 -57
- package/dist/core/agents/codex-replay.d.ts +0 -29
- package/dist/core/agents/codex-replay.d.ts.map +0 -1
- package/dist/core/agents/codex-replay.js +0 -138
- package/dist/core/agents/events/consume.d.ts +0 -82
- package/dist/core/agents/events/consume.d.ts.map +0 -1
- package/dist/core/agents/events/consume.js +0 -195
- package/dist/core/agents/events/emit.d.ts +0 -42
- package/dist/core/agents/events/emit.d.ts.map +0 -1
- package/dist/core/agents/events/emit.js +0 -75
- package/dist/core/agents/events/ulid.d.ts +0 -11
- package/dist/core/agents/events/ulid.d.ts.map +0 -1
- package/dist/core/agents/events/ulid.js +0 -47
- package/dist/core/agents/state/heartbeat-projector.d.ts +0 -65
- package/dist/core/agents/state/heartbeat-projector.d.ts.map +0 -1
- package/dist/core/agents/state/heartbeat-projector.js +0 -527
- package/dist/core/hooks/events/emit.d.ts +0 -31
- package/dist/core/hooks/events/emit.d.ts.map +0 -1
- package/dist/core/hooks/events/emit.js +0 -93
- package/dist/core/hooks/events/rotate.d.ts +0 -43
- package/dist/core/hooks/events/rotate.d.ts.map +0 -1
- package/dist/core/hooks/events/rotate.js +0 -142
- package/dist/core/hooks/events/schema.d.ts +0 -312
- package/dist/core/hooks/events/schema.d.ts.map +0 -1
- package/dist/core/hooks/events/schema.js +0 -12
- package/dist/core/hooks/events/ulid.d.ts +0 -10
- package/dist/core/hooks/events/ulid.d.ts.map +0 -1
- package/dist/core/hooks/events/ulid.js +0 -47
- package/dist/lib/docs-frontmatter-migrate.d.ts +0 -33
- package/dist/lib/docs-frontmatter-migrate.d.ts.map +0 -1
- package/dist/lib/docs-frontmatter-migrate.js +0 -364
- package/src/core/agents/cli-emit.ts +0 -64
- package/src/core/agents/codex-replay.ts +0 -163
- package/src/core/agents/events/consume.ts +0 -254
- package/src/core/agents/events/emit.ts +0 -115
- package/src/core/agents/events/ulid.ts +0 -51
- package/src/core/agents/state/heartbeat-projector.ts +0 -582
- package/src/core/hooks/events/emit.ts +0 -125
- package/src/core/hooks/events/rotate.ts +0 -151
- package/src/core/hooks/events/schema.ts +0 -538
- package/src/core/hooks/events/ulid.ts +0 -51
- package/src/lib/docs-frontmatter-migrate.ts +0 -427
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
// Visual-result reuse for critique tiles (plan 3.3): avoid re-judging pixels
|
|
2
|
+
// that did not change.
|
|
3
|
+
//
|
|
4
|
+
// Two mechanisms share this module; which is primary was decided by the
|
|
5
|
+
// Stage 0 byte-stability experiment, not preference:
|
|
6
|
+
//
|
|
7
|
+
// Path B (primary): band-level pixel diff. Compare each candidate tile's
|
|
8
|
+
// document-space rect in the CURRENT full-page screenshot against the SAME
|
|
9
|
+
// rect in the persisted baseline screenshot (qa-snapshot.ts), with
|
|
10
|
+
// pixelmatch's anti-aliasing tolerance. At or below a strict mismatch
|
|
11
|
+
// ratio (default 0.001), the tile region provably didn't change and the
|
|
12
|
+
// provider call is skipped. No byte identity required, so font
|
|
13
|
+
// rasterization and raster-path drift don't defeat it.
|
|
14
|
+
//
|
|
15
|
+
// Path A (observe mode: key computation + logging only): the Stage 0
|
|
16
|
+
// byte-stability experiment PASSED its gate (100% cross-restart identical
|
|
17
|
+
// bands on real pages, same-day/same-machine, deterministic settle), so an
|
|
18
|
+
// exact-digest cache is viable — but Path B subsumes its win (a
|
|
19
|
+
// byte-identical band pixel-diffs at ratio 0) with zero extra storage, so
|
|
20
|
+
// only the versioned cache key ships until telemetry shows a payoff Path B
|
|
21
|
+
// misses (e.g. cross-target tile sharing).
|
|
22
|
+
//
|
|
23
|
+
// Hard rules, both paths: never convert a prior failure into a pass — any
|
|
24
|
+
// baseline finding overlapping a tile's region forces a fresh review; a
|
|
25
|
+
// missing/invalid baseline, changed rubric, or changed critique contract is
|
|
26
|
+
// a miss, never an error.
|
|
27
|
+
|
|
28
|
+
import { createHash } from "node:crypto";
|
|
29
|
+
import pixelmatch from "pixelmatch";
|
|
30
|
+
import { PNG } from "pngjs";
|
|
31
|
+
import type { CritiqueFinding, CritiqueTile } from "./critique.js";
|
|
32
|
+
import { fnv1a32 } from "./qa-plan.js";
|
|
33
|
+
|
|
34
|
+
/** Bump when the critique request/response contract changes in a way that
|
|
35
|
+
* invalidates persisted findings (rubric semantics, finding schema, tiling
|
|
36
|
+
* geometry meaning). Part of every cache key and persisted critique. */
|
|
37
|
+
export const QA_CRITIQUE_CONTRACT_VERSION = 1;
|
|
38
|
+
|
|
39
|
+
/** Default Path B mismatch-ratio threshold. Strict on purpose: reuse trades
|
|
40
|
+
* essentially nothing for robustness against rasterization noise, and a
|
|
41
|
+
* looser threshold could hide a sub-threshold real change. */
|
|
42
|
+
export const DEFAULT_REUSE_MISMATCH_RATIO = 0.001;
|
|
43
|
+
|
|
44
|
+
/** Critique results persisted beside a QA snapshot, enough to decide reuse
|
|
45
|
+
* later: findings plus the geometry they were judged under. */
|
|
46
|
+
export interface PersistedCritique {
|
|
47
|
+
contract_version: number;
|
|
48
|
+
/** fnv1a32 of the exact rubric text the findings were judged under. */
|
|
49
|
+
rubric_digest: string;
|
|
50
|
+
outcome: "pass" | "fail";
|
|
51
|
+
findings: CritiqueFinding[];
|
|
52
|
+
tiles: Array<{
|
|
53
|
+
index: number;
|
|
54
|
+
label: string;
|
|
55
|
+
x: number;
|
|
56
|
+
scrollY: number;
|
|
57
|
+
width: number;
|
|
58
|
+
height: number;
|
|
59
|
+
}>;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Versioned, canonicalized Path A cache key (observe mode). Field order is
|
|
63
|
+
* part of the contract — never reorder without bumping the version. */
|
|
64
|
+
export function qaTileCacheKey(parts: {
|
|
65
|
+
tilePngSha256: string;
|
|
66
|
+
rubricDigest: string;
|
|
67
|
+
provider: string;
|
|
68
|
+
model: string;
|
|
69
|
+
viewport: string;
|
|
70
|
+
deviceScaleFactor: number;
|
|
71
|
+
theme: string;
|
|
72
|
+
state: string;
|
|
73
|
+
tileLabel: string;
|
|
74
|
+
browserVersion?: string;
|
|
75
|
+
}): string {
|
|
76
|
+
const canonical = [
|
|
77
|
+
`v${QA_CRITIQUE_CONTRACT_VERSION}`,
|
|
78
|
+
`png:${parts.tilePngSha256}`,
|
|
79
|
+
`rubric:${parts.rubricDigest}`,
|
|
80
|
+
`provider:${parts.provider}`,
|
|
81
|
+
`model:${parts.model}`,
|
|
82
|
+
`viewport:${parts.viewport}`,
|
|
83
|
+
`dpr:${parts.deviceScaleFactor}`,
|
|
84
|
+
`theme:${parts.theme}`,
|
|
85
|
+
`state:${parts.state}`,
|
|
86
|
+
`tile:${parts.tileLabel}`,
|
|
87
|
+
`browser:${parts.browserVersion ?? "unknown"}`,
|
|
88
|
+
].join("\x1f");
|
|
89
|
+
return `qa1-${createHash("sha256").update(canonical).digest("hex")}`;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function sha256Hex(buf: Buffer | Uint8Array): string {
|
|
93
|
+
return createHash("sha256").update(buf).digest("hex");
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function rubricDigest(rubric: string): string {
|
|
97
|
+
// Whitespace-normalized so formatting-only edits don't invalidate reuse.
|
|
98
|
+
return fnv1a32(rubric.replace(/\s+/g, " ").trim());
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function cropRegion(
|
|
102
|
+
png: PNG,
|
|
103
|
+
rect: { x: number; y: number; width: number; height: number },
|
|
104
|
+
): PNG | null {
|
|
105
|
+
const sx = Math.max(0, Math.min(Math.round(rect.x), png.width - 1));
|
|
106
|
+
const sy = Math.max(0, Math.min(Math.round(rect.y), png.height - 1));
|
|
107
|
+
const w = Math.min(Math.round(rect.width), png.width - sx);
|
|
108
|
+
const h = Math.min(Math.round(rect.height), png.height - sy);
|
|
109
|
+
if (w <= 0 || h <= 0) return null;
|
|
110
|
+
const out = new PNG({ width: w, height: h });
|
|
111
|
+
for (let row = 0; row < h; row++) {
|
|
112
|
+
const srcStart = ((sy + row) * png.width + sx) * 4;
|
|
113
|
+
png.data.copy(out.data, row * w * 4, srcStart, srcStart + w * 4);
|
|
114
|
+
}
|
|
115
|
+
return out;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Pixel-compare one document-space rect across two full-page screenshots.
|
|
120
|
+
* `sizeMismatch` is true when the rect isn't fully comparable in both images
|
|
121
|
+
* (page grew/shrank past it) — the caller treats that as changed.
|
|
122
|
+
*/
|
|
123
|
+
export function rectMismatch(
|
|
124
|
+
baselineFullPage: PNG,
|
|
125
|
+
currentFullPage: PNG,
|
|
126
|
+
rect: { x: number; y: number; width: number; height: number },
|
|
127
|
+
opts: { pixelThreshold?: number } = {},
|
|
128
|
+
): { ratio: number; comparablePixels: number; sizeMismatch: boolean } {
|
|
129
|
+
const base = cropRegion(baselineFullPage, rect);
|
|
130
|
+
const cur = cropRegion(currentFullPage, rect);
|
|
131
|
+
if (!base || !cur || base.width !== cur.width || base.height !== cur.height) {
|
|
132
|
+
return { ratio: 1, comparablePixels: 0, sizeMismatch: true };
|
|
133
|
+
}
|
|
134
|
+
const mismatched = pixelmatch(base.data, cur.data, undefined, base.width, base.height, {
|
|
135
|
+
threshold: opts.pixelThreshold ?? 0.1,
|
|
136
|
+
includeAA: false,
|
|
137
|
+
});
|
|
138
|
+
const total = base.width * base.height;
|
|
139
|
+
return { ratio: mismatched / total, comparablePixels: total, sizeMismatch: false };
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export interface TileReuseDecision {
|
|
143
|
+
index: number;
|
|
144
|
+
label: string;
|
|
145
|
+
reuse: boolean;
|
|
146
|
+
reason: string;
|
|
147
|
+
/** Rect mismatch ratio, present when a pixel comparison ran. */
|
|
148
|
+
mismatch_ratio?: number;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export interface CritiqueReusePlan {
|
|
152
|
+
mode: "band-diff";
|
|
153
|
+
/** Tiles that must be reviewed fresh, in original order. */
|
|
154
|
+
review: CritiqueTile[];
|
|
155
|
+
decisions: TileReuseDecision[];
|
|
156
|
+
tiles_total: number;
|
|
157
|
+
tiles_reused: number;
|
|
158
|
+
tiles_reviewed: number;
|
|
159
|
+
provider_calls_avoided: number;
|
|
160
|
+
mismatch_threshold: number;
|
|
161
|
+
/** Set when the whole baseline was unusable (everything reviews fresh). */
|
|
162
|
+
invalidation?: string;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function wholeRunMiss(
|
|
166
|
+
tiles: CritiqueTile[],
|
|
167
|
+
threshold: number,
|
|
168
|
+
invalidation: string,
|
|
169
|
+
): CritiqueReusePlan {
|
|
170
|
+
return {
|
|
171
|
+
mode: "band-diff",
|
|
172
|
+
review: tiles,
|
|
173
|
+
decisions: tiles.map((t) => ({
|
|
174
|
+
index: t.index,
|
|
175
|
+
label: t.label,
|
|
176
|
+
reuse: false,
|
|
177
|
+
reason: invalidation,
|
|
178
|
+
})),
|
|
179
|
+
tiles_total: tiles.length,
|
|
180
|
+
tiles_reused: 0,
|
|
181
|
+
tiles_reviewed: tiles.length,
|
|
182
|
+
provider_calls_avoided: 0,
|
|
183
|
+
mismatch_threshold: threshold,
|
|
184
|
+
invalidation,
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Path B reuse plan: decide per tile whether the baseline run's verdict still
|
|
190
|
+
* covers it. A tile is reused ONLY when (a) the baseline is valid for this
|
|
191
|
+
* rubric and contract, (b) no baseline finding overlaps the tile's document
|
|
192
|
+
* region (a prior failure is never converted into a pass), and (c) its rect
|
|
193
|
+
* pixel-matches the baseline screenshot at or below the strict threshold.
|
|
194
|
+
* Everything else reviews fresh — reuse can produce false REVIEWS, never
|
|
195
|
+
* false passes.
|
|
196
|
+
*/
|
|
197
|
+
export function planCritiqueReuse(args: {
|
|
198
|
+
baselineScreenshot: Buffer;
|
|
199
|
+
baselineCritique: PersistedCritique;
|
|
200
|
+
currentScreenshot: Buffer;
|
|
201
|
+
tiles: CritiqueTile[];
|
|
202
|
+
rubric: string;
|
|
203
|
+
mismatchThreshold?: number;
|
|
204
|
+
}): CritiqueReusePlan {
|
|
205
|
+
const threshold = args.mismatchThreshold ?? DEFAULT_REUSE_MISMATCH_RATIO;
|
|
206
|
+
const { baselineCritique, tiles } = args;
|
|
207
|
+
if (baselineCritique.outcome !== "pass") {
|
|
208
|
+
return wholeRunMiss(tiles, threshold, "baseline critique did not pass");
|
|
209
|
+
}
|
|
210
|
+
if (baselineCritique.contract_version !== QA_CRITIQUE_CONTRACT_VERSION) {
|
|
211
|
+
return wholeRunMiss(
|
|
212
|
+
tiles,
|
|
213
|
+
threshold,
|
|
214
|
+
`baseline critique contract v${baselineCritique.contract_version} != v${QA_CRITIQUE_CONTRACT_VERSION}`,
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
if (baselineCritique.rubric_digest !== rubricDigest(args.rubric)) {
|
|
218
|
+
return wholeRunMiss(tiles, threshold, "rubric changed since the baseline run");
|
|
219
|
+
}
|
|
220
|
+
let baselinePng: PNG;
|
|
221
|
+
let currentPng: PNG;
|
|
222
|
+
try {
|
|
223
|
+
baselinePng = PNG.sync.read(args.baselineScreenshot);
|
|
224
|
+
currentPng = PNG.sync.read(args.currentScreenshot);
|
|
225
|
+
} catch {
|
|
226
|
+
return wholeRunMiss(tiles, threshold, "unreadable baseline or current screenshot");
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Document regions of baseline tiles that produced findings.
|
|
230
|
+
const findingTiles = new Set(baselineCritique.findings.map((f) => f.tile));
|
|
231
|
+
const dirtyRegions = baselineCritique.tiles
|
|
232
|
+
.filter((t) => findingTiles.has(t.index))
|
|
233
|
+
.map((t) => ({ top: t.scrollY, bottom: t.scrollY + t.height }));
|
|
234
|
+
|
|
235
|
+
const decisions: TileReuseDecision[] = [];
|
|
236
|
+
const review: CritiqueTile[] = [];
|
|
237
|
+
for (const tile of tiles) {
|
|
238
|
+
const top = tile.scrollY;
|
|
239
|
+
const bottom = tile.scrollY + tile.height;
|
|
240
|
+
const left = tile.x ?? 0;
|
|
241
|
+
const right = left + tile.width;
|
|
242
|
+
const coverage = baselineCritique.tiles
|
|
243
|
+
.filter((prior) => prior.x <= left && prior.x + prior.width >= right)
|
|
244
|
+
.map((prior) => ({ top: prior.scrollY, bottom: prior.scrollY + prior.height }))
|
|
245
|
+
.sort((a, b) => a.top - b.top);
|
|
246
|
+
let coveredTo = top;
|
|
247
|
+
for (const region of coverage) {
|
|
248
|
+
if (region.bottom <= coveredTo || region.top > coveredTo) continue;
|
|
249
|
+
coveredTo = Math.max(coveredTo, region.bottom);
|
|
250
|
+
if (coveredTo >= bottom) break;
|
|
251
|
+
}
|
|
252
|
+
if (coveredTo < bottom) {
|
|
253
|
+
decisions.push({
|
|
254
|
+
index: tile.index,
|
|
255
|
+
label: tile.label,
|
|
256
|
+
reuse: false,
|
|
257
|
+
reason: "baseline critique did not cover this entire region",
|
|
258
|
+
});
|
|
259
|
+
review.push(tile);
|
|
260
|
+
continue;
|
|
261
|
+
}
|
|
262
|
+
const overlapsFinding = dirtyRegions.some((r) => r.top < bottom && r.bottom > top);
|
|
263
|
+
if (overlapsFinding) {
|
|
264
|
+
decisions.push({
|
|
265
|
+
index: tile.index,
|
|
266
|
+
label: tile.label,
|
|
267
|
+
reuse: false,
|
|
268
|
+
reason: "baseline run had findings in this region — always re-reviewed",
|
|
269
|
+
});
|
|
270
|
+
review.push(tile);
|
|
271
|
+
continue;
|
|
272
|
+
}
|
|
273
|
+
const rect = { x: tile.x ?? 0, y: tile.scrollY, width: tile.width, height: tile.height };
|
|
274
|
+
const cmp = rectMismatch(baselinePng, currentPng, rect);
|
|
275
|
+
if (cmp.sizeMismatch || cmp.ratio > threshold) {
|
|
276
|
+
decisions.push({
|
|
277
|
+
index: tile.index,
|
|
278
|
+
label: tile.label,
|
|
279
|
+
reuse: false,
|
|
280
|
+
reason: cmp.sizeMismatch
|
|
281
|
+
? "region not comparable across screenshots (layout size changed)"
|
|
282
|
+
: `mismatch ratio ${cmp.ratio.toFixed(6)} > ${threshold}`,
|
|
283
|
+
mismatch_ratio: cmp.sizeMismatch ? undefined : cmp.ratio,
|
|
284
|
+
});
|
|
285
|
+
review.push(tile);
|
|
286
|
+
continue;
|
|
287
|
+
}
|
|
288
|
+
decisions.push({
|
|
289
|
+
index: tile.index,
|
|
290
|
+
label: tile.label,
|
|
291
|
+
reuse: true,
|
|
292
|
+
reason: `clean region pixel-matched baseline (ratio ${cmp.ratio.toFixed(6)})`,
|
|
293
|
+
mismatch_ratio: cmp.ratio,
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
return {
|
|
297
|
+
mode: "band-diff",
|
|
298
|
+
review,
|
|
299
|
+
decisions,
|
|
300
|
+
tiles_total: tiles.length,
|
|
301
|
+
tiles_reused: tiles.length - review.length,
|
|
302
|
+
tiles_reviewed: review.length,
|
|
303
|
+
provider_calls_avoided: tiles.length - review.length,
|
|
304
|
+
mismatch_threshold: threshold,
|
|
305
|
+
};
|
|
306
|
+
}
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
// Persisted QA snapshots: the baseline source that makes repeat QA cheap.
|
|
2
|
+
//
|
|
3
|
+
// Every passing QA run persists its final page signature (and optionally the
|
|
4
|
+
// serialized DOM + full-page screenshot) per render context. The next run
|
|
5
|
+
// inherits its baseline for free instead of re-rendering production or a git
|
|
6
|
+
// revision. Resolution order (the plan's 2.6):
|
|
7
|
+
// 1. persisted QA snapshot (this store)
|
|
8
|
+
// 2. production render (caller-supplied fallback)
|
|
9
|
+
// 3. baseline-revision render (caller-supplied fallback, from a worktree)
|
|
10
|
+
// 4. none → classification is `unknown`, never text-only
|
|
11
|
+
//
|
|
12
|
+
// Storage is content-under-a-key, like visual-diff baselines: a directory per
|
|
13
|
+
// target, a subdirectory per context, atomic writes (tmp + rename) so a
|
|
14
|
+
// crashed writer never leaves a half-baseline the next run would trust.
|
|
15
|
+
|
|
16
|
+
import {
|
|
17
|
+
existsSync,
|
|
18
|
+
mkdirSync,
|
|
19
|
+
readdirSync,
|
|
20
|
+
readFileSync,
|
|
21
|
+
renameSync,
|
|
22
|
+
rmSync,
|
|
23
|
+
writeFileSync,
|
|
24
|
+
} from "node:fs";
|
|
25
|
+
import { homedir } from "node:os";
|
|
26
|
+
import { resolve } from "node:path";
|
|
27
|
+
import { fnv1a32, type QaContext, type QaSignature } from "./qa-plan.js";
|
|
28
|
+
import type { PersistedCritique } from "./qa-reuse.js";
|
|
29
|
+
|
|
30
|
+
const SNAPSHOT_DIR = resolve(homedir(), ".cache", "harnery", "qa-snapshots");
|
|
31
|
+
|
|
32
|
+
export interface QaSnapshotStoreOptions {
|
|
33
|
+
/** Override the store root (tests, host-managed cache locations). */
|
|
34
|
+
root?: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface StoredQaSnapshot {
|
|
38
|
+
signature: QaSignature;
|
|
39
|
+
/** Directory holding this snapshot's files. */
|
|
40
|
+
path: string;
|
|
41
|
+
domHtmlPath?: string;
|
|
42
|
+
screenshotPath?: string;
|
|
43
|
+
/** Critique results persisted with the snapshot (full-coverage runs only) —
|
|
44
|
+
* what the band-diff reuse layer replays for provably-unchanged regions. */
|
|
45
|
+
critique?: PersistedCritique;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Filesystem-safe key for a target URL/file + render context. The digest
|
|
49
|
+
* disambiguates targets whose sanitized slugs collide. */
|
|
50
|
+
export function qaSnapshotKey(target: string): string {
|
|
51
|
+
const source = target.replace(/^[a-z]+:\/\//i, "");
|
|
52
|
+
let slug = "";
|
|
53
|
+
let separatorPending = false;
|
|
54
|
+
for (let index = 0; index < source.length && slug.length < 80; index++) {
|
|
55
|
+
const code = source.charCodeAt(index);
|
|
56
|
+
const alpha = (code >= 65 && code <= 90) || (code >= 97 && code <= 122);
|
|
57
|
+
const digit = code >= 48 && code <= 57;
|
|
58
|
+
if (alpha || digit) {
|
|
59
|
+
if (separatorPending && slug.length > 0 && slug.length < 80) slug += "-";
|
|
60
|
+
if (slug.length < 80) slug += source[index];
|
|
61
|
+
separatorPending = false;
|
|
62
|
+
} else if (slug.length > 0) {
|
|
63
|
+
separatorPending = true;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return `${slug || "target"}-${fnv1a32(target).slice(0, 8)}`;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function contextDirName(context: QaContext): string {
|
|
70
|
+
const clean = (part: string) => part.replace(/[^a-zA-Z0-9_.]+/g, "-");
|
|
71
|
+
return `${clean(context.viewport)}-${clean(context.theme)}-${clean(context.state)}`;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function snapshotDir(target: string, context: QaContext, opts: QaSnapshotStoreOptions): string {
|
|
75
|
+
return resolve(opts.root ?? SNAPSHOT_DIR, qaSnapshotKey(target), contextDirName(context));
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Persist a QA snapshot atomically. The whole context directory is staged as
|
|
80
|
+
* `<dir>.tmp-<pid>` and renamed into place, replacing any prior snapshot for
|
|
81
|
+
* the same target + context — a reader never observes a partial write.
|
|
82
|
+
*/
|
|
83
|
+
export function saveQaSnapshot(
|
|
84
|
+
target: string,
|
|
85
|
+
context: QaContext,
|
|
86
|
+
content: {
|
|
87
|
+
signature: QaSignature;
|
|
88
|
+
domHtml?: string;
|
|
89
|
+
screenshotPng?: Buffer;
|
|
90
|
+
critique?: PersistedCritique;
|
|
91
|
+
},
|
|
92
|
+
opts: QaSnapshotStoreOptions = {},
|
|
93
|
+
): StoredQaSnapshot {
|
|
94
|
+
const dir = snapshotDir(target, context, opts);
|
|
95
|
+
const staging = `${dir}.tmp-${process.pid}`;
|
|
96
|
+
rmSync(staging, { recursive: true, force: true });
|
|
97
|
+
mkdirSync(staging, { recursive: true });
|
|
98
|
+
writeFileSync(resolve(staging, "signature.json"), `${JSON.stringify(content.signature)}\n`);
|
|
99
|
+
if (content.domHtml !== undefined) {
|
|
100
|
+
writeFileSync(resolve(staging, "dom.html"), content.domHtml);
|
|
101
|
+
}
|
|
102
|
+
if (content.screenshotPng !== undefined) {
|
|
103
|
+
writeFileSync(resolve(staging, "screenshot.png"), content.screenshotPng);
|
|
104
|
+
}
|
|
105
|
+
if (content.critique !== undefined) {
|
|
106
|
+
writeFileSync(resolve(staging, "critique.json"), `${JSON.stringify(content.critique)}\n`);
|
|
107
|
+
}
|
|
108
|
+
rmSync(dir, { recursive: true, force: true });
|
|
109
|
+
renameSync(staging, dir);
|
|
110
|
+
return {
|
|
111
|
+
signature: content.signature,
|
|
112
|
+
path: dir,
|
|
113
|
+
...(content.domHtml !== undefined ? { domHtmlPath: resolve(dir, "dom.html") } : {}),
|
|
114
|
+
...(content.screenshotPng !== undefined
|
|
115
|
+
? { screenshotPath: resolve(dir, "screenshot.png") }
|
|
116
|
+
: {}),
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** Load the persisted snapshot for a target + context, or null. A corrupt
|
|
121
|
+
* signature file returns null (and the caller re-acquires a baseline) rather
|
|
122
|
+
* than throwing — a bad cache entry must never block QA. */
|
|
123
|
+
export function loadQaSnapshot(
|
|
124
|
+
target: string,
|
|
125
|
+
context: QaContext,
|
|
126
|
+
opts: QaSnapshotStoreOptions = {},
|
|
127
|
+
): StoredQaSnapshot | null {
|
|
128
|
+
const dir = snapshotDir(target, context, opts);
|
|
129
|
+
const sigPath = resolve(dir, "signature.json");
|
|
130
|
+
if (!existsSync(sigPath)) return null;
|
|
131
|
+
try {
|
|
132
|
+
const signature = JSON.parse(readFileSync(sigPath, "utf8")) as QaSignature;
|
|
133
|
+
if (!signature || !Array.isArray(signature.nodes) || !Array.isArray(signature.stylesheets)) {
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
const domHtmlPath = resolve(dir, "dom.html");
|
|
137
|
+
const screenshotPath = resolve(dir, "screenshot.png");
|
|
138
|
+
const critiquePath = resolve(dir, "critique.json");
|
|
139
|
+
let critique: PersistedCritique | undefined;
|
|
140
|
+
if (existsSync(critiquePath)) {
|
|
141
|
+
try {
|
|
142
|
+
const parsed = JSON.parse(readFileSync(critiquePath, "utf8")) as PersistedCritique;
|
|
143
|
+
if (parsed && typeof parsed.contract_version === "number" && Array.isArray(parsed.tiles)) {
|
|
144
|
+
critique = parsed;
|
|
145
|
+
}
|
|
146
|
+
} catch {
|
|
147
|
+
// corrupt critique.json degrades to a reuse miss, never an error
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return {
|
|
151
|
+
signature,
|
|
152
|
+
path: dir,
|
|
153
|
+
...(existsSync(domHtmlPath) ? { domHtmlPath } : {}),
|
|
154
|
+
...(existsSync(screenshotPath) ? { screenshotPath } : {}),
|
|
155
|
+
...(critique ? { critique } : {}),
|
|
156
|
+
};
|
|
157
|
+
} catch {
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/** List targets with at least one persisted snapshot (store maintenance). */
|
|
163
|
+
export function listQaSnapshotTargets(opts: QaSnapshotStoreOptions = {}): string[] {
|
|
164
|
+
const root = opts.root ?? SNAPSHOT_DIR;
|
|
165
|
+
if (!existsSync(root)) return [];
|
|
166
|
+
try {
|
|
167
|
+
return readdirSync(root)
|
|
168
|
+
.filter((name) => !name.includes(".tmp-"))
|
|
169
|
+
.sort();
|
|
170
|
+
} catch {
|
|
171
|
+
return [];
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export interface ResolvedQaBaseline {
|
|
176
|
+
/** `qa-snapshot:<capturedAt>` | `production-render:<capturedAt>` |
|
|
177
|
+
* `revision-render:<label>` | `none` — recorded in the manifest so a
|
|
178
|
+
* reviewer can judge staleness. */
|
|
179
|
+
source: string;
|
|
180
|
+
signature: QaSignature | null;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Walk the baseline resolution order with caller-supplied fallbacks. Pure
|
|
185
|
+
* orchestration: rendering lives in the caller (production URLs and git
|
|
186
|
+
* worktrees are its business), this just encodes the order and the source
|
|
187
|
+
* labeling. A fallback that throws or returns null falls through to the next
|
|
188
|
+
* rung; exhausting all rungs returns `{ source: "none", signature: null }`,
|
|
189
|
+
* which classifies as `unknown` — never silently text-only.
|
|
190
|
+
*/
|
|
191
|
+
export async function resolveQaBaseline(opts: {
|
|
192
|
+
target: string;
|
|
193
|
+
context: QaContext;
|
|
194
|
+
store?: QaSnapshotStoreOptions;
|
|
195
|
+
renderProduction?: () => Promise<QaSignature | null>;
|
|
196
|
+
renderRevision?: { label: string; render: () => Promise<QaSignature | null> };
|
|
197
|
+
}): Promise<ResolvedQaBaseline> {
|
|
198
|
+
const stored = loadQaSnapshot(opts.target, opts.context, opts.store ?? {});
|
|
199
|
+
if (stored) {
|
|
200
|
+
return { source: `qa-snapshot:${stored.signature.capturedAt}`, signature: stored.signature };
|
|
201
|
+
}
|
|
202
|
+
if (opts.renderProduction) {
|
|
203
|
+
try {
|
|
204
|
+
const signature = await opts.renderProduction();
|
|
205
|
+
if (signature) return { source: `production-render:${signature.capturedAt}`, signature };
|
|
206
|
+
} catch {
|
|
207
|
+
// fall through — an unreachable production URL is not a verdict
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
if (opts.renderRevision) {
|
|
211
|
+
try {
|
|
212
|
+
const signature = await opts.renderRevision.render();
|
|
213
|
+
if (signature) {
|
|
214
|
+
return { source: `revision-render:${opts.renderRevision.label}`, signature };
|
|
215
|
+
}
|
|
216
|
+
} catch {
|
|
217
|
+
// fall through
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
return { source: "none", signature: null };
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
export { SNAPSHOT_DIR as QA_SNAPSHOT_DIR };
|
package/src/lib/browser/runts.ts
CHANGED
|
@@ -129,7 +129,7 @@ export function buildRuntsCheck(): (args: {
|
|
|
129
129
|
scanned++;
|
|
130
130
|
|
|
131
131
|
// Map every word to its visual line by rounding the Range rect top.
|
|
132
|
-
const lines = new Map<number, { words: string[]; last: DOMRect }>();
|
|
132
|
+
const lines = new Map<number, { words: string[]; last: DOMRect; node: Node }>();
|
|
133
133
|
const tw = document.createTreeWalker(el, NodeFilter.SHOW_TEXT);
|
|
134
134
|
for (let n = tw.nextNode(); n; n = tw.nextNode()) {
|
|
135
135
|
const value = n.nodeValue ?? "";
|
|
@@ -144,8 +144,9 @@ export function buildRuntsCheck(): (args: {
|
|
|
144
144
|
if (entry) {
|
|
145
145
|
entry.words.push(m[0]);
|
|
146
146
|
entry.last = rect;
|
|
147
|
+
entry.node = n;
|
|
147
148
|
} else {
|
|
148
|
-
lines.set(top, { words: [m[0]], last: rect });
|
|
149
|
+
lines.set(top, { words: [m[0]], last: rect, node: n });
|
|
149
150
|
}
|
|
150
151
|
}
|
|
151
152
|
}
|
|
@@ -156,6 +157,30 @@ export function buildRuntsCheck(): (args: {
|
|
|
156
157
|
if (last?.words.length !== 1) continue;
|
|
157
158
|
const word = last.words[0] ?? "";
|
|
158
159
|
if (ATOMIC.test(word)) continue;
|
|
160
|
+
// A chip is not prose. A lone token whose glyphs live inside a padded,
|
|
161
|
+
// boxed inline element (a <code> chip, pill, or badge) is a UI control:
|
|
162
|
+
// it cannot be rebalanced by rewording, tag rows legitimately wrap to a
|
|
163
|
+
// single chip, and its shifted glyph box also buckets as a phantom
|
|
164
|
+
// "line" when it sits mid-line beside normal text. Walk the word's
|
|
165
|
+
// inline ancestry (tag-based, since flex/grid parents blockify computed
|
|
166
|
+
// display) and skip the finding when a chip hosts it.
|
|
167
|
+
const chipHosted = (() => {
|
|
168
|
+
for (
|
|
169
|
+
let e = last.node.parentElement;
|
|
170
|
+
e && e !== el && INLINE.has(e.tagName);
|
|
171
|
+
e = e.parentElement
|
|
172
|
+
) {
|
|
173
|
+
const cs = getComputedStyle(e);
|
|
174
|
+
const padX =
|
|
175
|
+
(Number.parseFloat(cs.paddingLeft) || 0) + (Number.parseFloat(cs.paddingRight) || 0);
|
|
176
|
+
const boxed =
|
|
177
|
+
(cs.backgroundColor !== "rgba(0, 0, 0, 0)" && cs.backgroundColor !== "transparent") ||
|
|
178
|
+
(Number.parseFloat(cs.borderTopWidth) || 0) > 0;
|
|
179
|
+
if (padX > 0 && boxed) return true;
|
|
180
|
+
}
|
|
181
|
+
return false;
|
|
182
|
+
})();
|
|
183
|
+
if (chipHosted) continue;
|
|
159
184
|
|
|
160
185
|
runts.push({
|
|
161
186
|
block: label(el),
|