harnery 0.30.0 → 0.31.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 +4 -4
- package/dist/commander.d.ts +8 -0
- package/dist/commander.d.ts.map +1 -1
- package/dist/commander.js +14 -13
- package/dist/commands/adapter.d.ts +12 -0
- package/dist/commands/adapter.d.ts.map +1 -0
- package/dist/commands/{harness.js → adapter.js} +35 -35
- package/dist/commands/agents.d.ts +3 -2
- package/dist/commands/agents.d.ts.map +1 -1
- package/dist/commands/agents.js +61 -60
- package/dist/commands/artifacts.d.ts +4 -0
- package/dist/commands/artifacts.d.ts.map +1 -0
- package/dist/commands/artifacts.js +143 -0
- package/dist/commands/backup.d.ts +1 -1
- package/dist/commands/backup.d.ts.map +1 -1
- package/dist/commands/backup.js +5 -2
- package/dist/commands/browse.d.ts.map +1 -1
- package/dist/commands/browse.js +310 -7
- package/dist/commands/checkpoint.d.ts +12 -0
- package/dist/commands/checkpoint.d.ts.map +1 -0
- package/dist/commands/checkpoint.js +162 -0
- package/dist/commands/context.d.ts +2 -2
- package/dist/commands/context.d.ts.map +1 -1
- package/dist/commands/context.js +3 -151
- package/dist/commands/decision.d.ts.map +1 -1
- package/dist/commands/decision.js +31 -7
- package/dist/commands/deinit.d.ts +2 -2
- package/dist/commands/deinit.js +15 -15
- package/dist/commands/doctor.js +23 -23
- package/dist/commands/governor.d.ts +4 -0
- package/dist/commands/governor.d.ts.map +1 -0
- package/dist/commands/{supervisor.js → governor.js} +133 -108
- package/dist/commands/init.d.ts +12 -12
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +44 -30
- package/dist/commands/{session.d.ts → journal.d.ts} +2 -2
- package/dist/commands/journal.d.ts.map +1 -0
- package/dist/commands/{scratch.js → journal.js} +47 -47
- package/dist/commands/policy.js +1 -1
- package/dist/commands/presence.js +2 -2
- package/dist/commands/sync.d.ts +1 -1
- package/dist/commands/sync.js +3 -3
- package/dist/commands/tunnel.d.ts +16 -0
- package/dist/commands/tunnel.d.ts.map +1 -1
- package/dist/commands/tunnel.js +193 -23
- package/dist/commands/web-fetch.js +1 -1
- package/dist/commands/web.d.ts +19 -0
- package/dist/commands/web.d.ts.map +1 -1
- package/dist/commands/web.js +51 -0
- package/dist/commands/work.d.ts.map +1 -1
- package/dist/commands/work.js +38 -22
- package/dist/commands/workflow.d.ts.map +1 -1
- package/dist/commands/workflow.js +35 -28
- package/dist/core/{harnesses → adapters}/attest-projection.d.ts +2 -2
- package/dist/core/adapters/attest-projection.d.ts.map +1 -0
- package/dist/core/{harnesses → adapters}/attest.d.ts +16 -16
- package/dist/core/adapters/attest.d.ts.map +1 -0
- package/dist/core/{harnesses → adapters}/attest.js +9 -9
- package/dist/core/{harnesses → adapters}/attestation.d.ts +16 -16
- package/dist/core/adapters/attestation.d.ts.map +1 -0
- package/dist/core/{harnesses → adapters}/attestation.js +20 -20
- package/dist/core/{harnesses → adapters}/bench.d.ts +12 -12
- package/dist/core/adapters/bench.d.ts.map +1 -0
- package/dist/core/{harnesses → adapters}/bench.js +12 -12
- package/dist/core/adapters/index.d.ts +13 -0
- package/dist/core/adapters/index.d.ts.map +1 -0
- package/dist/core/adapters/index.js +6 -0
- package/dist/core/{harnesses → adapters}/profiles.d.ts +11 -11
- package/dist/core/adapters/profiles.d.ts.map +1 -0
- package/dist/core/{harnesses → adapters}/profiles.js +11 -11
- package/dist/core/adapters/registry.d.ts +15 -0
- package/dist/core/adapters/registry.d.ts.map +1 -0
- package/dist/core/{harnesses → adapters}/registry.js +11 -11
- package/dist/core/{harnesses → adapters}/types.d.ts +23 -23
- package/dist/core/adapters/types.d.ts.map +1 -0
- package/dist/core/{harnesses → adapters}/types.js +1 -1
- package/dist/core/agents/canonical-emit.d.ts +4 -4
- package/dist/core/agents/canonical-emit.js +5 -5
- package/dist/core/agents/cli-emit.js +1 -1
- package/dist/core/agents/cli.js +30 -26
- package/dist/core/agents/codex-replay.d.ts +1 -1
- package/dist/core/agents/codex-replay.js +6 -6
- package/dist/core/agents/coord-client.d.ts +21 -2
- package/dist/core/agents/coord-client.d.ts.map +1 -1
- package/dist/core/agents/coord-client.js +122 -27
- package/dist/core/agents/events/consume.d.ts +1 -1
- package/dist/core/agents/events/emit.d.ts +5 -6
- package/dist/core/agents/events/emit.d.ts.map +1 -1
- package/dist/core/agents/events/emit.js +2 -2
- package/dist/core/agents/render/prompt-context.d.ts +21 -9
- package/dist/core/agents/render/prompt-context.d.ts.map +1 -1
- package/dist/core/agents/render/prompt-context.js +82 -30
- package/dist/core/agents/render/session-context.d.ts +1 -1
- package/dist/core/agents/render/session-context.js +5 -5
- package/dist/core/agents/rules/claim-conflict.d.ts +2 -6
- package/dist/core/agents/rules/claim-conflict.d.ts.map +1 -1
- package/dist/core/agents/rules/claim-conflict.js +1 -1
- package/dist/core/agents/rules/commit-conflict.js +98 -22
- package/dist/core/agents/rules/stop-hook.d.ts +9 -9
- package/dist/core/agents/rules/stop-hook.d.ts.map +1 -1
- package/dist/core/agents/rules/stop-hook.js +23 -5
- package/dist/core/agents/rules/verdict.d.ts +14 -0
- package/dist/core/agents/rules/verdict.d.ts.map +1 -0
- package/dist/core/agents/rules/verdict.js +1 -0
- package/dist/core/agents/session-events.js +3 -3
- package/dist/core/agents/state/council.d.ts +1 -1
- package/dist/core/agents/state/council.js +1 -1
- package/dist/core/agents/state/heartbeat-projector.d.ts +3 -2
- package/dist/core/agents/state/heartbeat-projector.d.ts.map +1 -1
- package/dist/core/agents/state/heartbeat-projector.js +14 -10
- package/dist/core/agents/state/heartbeat-writer.d.ts +1 -1
- package/dist/core/agents/state/heartbeat-writer.js +12 -12
- package/dist/core/agents/state/{scratch.d.ts → journal.d.ts} +7 -7
- package/dist/core/agents/state/{scratch.d.ts.map → journal.d.ts.map} +1 -1
- package/dist/core/agents/state/{scratch.js → journal.js} +12 -12
- package/dist/core/agents/state/pidmap.d.ts +28 -4
- package/dist/core/agents/state/pidmap.d.ts.map +1 -1
- package/dist/core/agents/state/pidmap.js +130 -15
- package/dist/core/agents/state/proc-start.d.ts +95 -0
- package/dist/core/agents/state/proc-start.d.ts.map +1 -0
- package/dist/core/agents/state/proc-start.js +199 -0
- package/dist/core/agents/state/shell-mutation.d.ts +1 -1
- package/dist/core/agents/state/shell-mutation.js +1 -1
- package/dist/core/agents/state/stale-sweep.js +7 -7
- package/dist/core/artifacts/index.d.ts +86 -0
- package/dist/core/artifacts/index.d.ts.map +1 -0
- package/dist/core/artifacts/index.js +421 -0
- package/dist/core/config.d.ts +9 -2
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +26 -2
- package/dist/core/context/index.d.ts +12 -12
- package/dist/core/context/index.js +10 -10
- package/dist/core/governor/adapter-spread.d.ts +52 -0
- package/dist/core/governor/adapter-spread.d.ts.map +1 -0
- package/dist/core/governor/adapter-spread.js +52 -0
- package/dist/core/governor/index.d.ts +7 -0
- package/dist/core/governor/index.d.ts.map +1 -0
- package/dist/core/governor/index.js +6 -0
- package/dist/core/governor/plan-api.d.ts +38 -0
- package/dist/core/governor/plan-api.d.ts.map +1 -0
- package/dist/core/governor/plan-api.js +83 -0
- package/dist/core/governor/plan-read.d.ts +5 -0
- package/dist/core/governor/plan-read.d.ts.map +1 -0
- package/dist/core/{supervisor → governor}/plan-read.js +66 -52
- package/dist/core/{supervisor → governor}/plan-types.d.ts +57 -56
- package/dist/core/governor/plan-types.d.ts.map +1 -0
- package/dist/core/{supervisor → governor}/plan-types.js +4 -4
- package/dist/core/governor/planning.d.ts +48 -0
- package/dist/core/governor/planning.d.ts.map +1 -0
- package/dist/core/{supervisor → governor}/planning.js +125 -94
- package/dist/core/governor/read.d.ts +6 -0
- package/dist/core/governor/read.d.ts.map +1 -0
- package/dist/core/governor/read.js +4 -0
- package/dist/core/governor/runner.d.ts +37 -0
- package/dist/core/governor/runner.d.ts.map +1 -0
- package/dist/core/{supervisor → governor}/runner.js +15 -15
- package/dist/core/governor/service-read.d.ts +6 -0
- package/dist/core/governor/service-read.d.ts.map +1 -0
- package/dist/core/{supervisor → governor}/service-read.js +12 -12
- package/dist/core/governor/service.d.ts +114 -0
- package/dist/core/governor/service.d.ts.map +1 -0
- package/dist/core/{supervisor → governor}/service.js +57 -57
- package/dist/core/governor/state.d.ts +116 -0
- package/dist/core/governor/state.d.ts.map +1 -0
- package/dist/core/{supervisor → governor}/state.js +228 -108
- package/dist/core/hooks/adapter/detect.d.ts +9 -0
- package/dist/core/hooks/{harness → adapter}/detect.d.ts.map +1 -1
- package/dist/core/hooks/{harness → adapter}/detect.js +8 -8
- package/dist/core/hooks/{harness → adapter}/events.d.ts +15 -14
- package/dist/core/hooks/adapter/events.d.ts.map +1 -0
- package/dist/core/hooks/{harness → adapter}/events.js +5 -5
- package/dist/core/hooks/{harness → adapter}/output.d.ts +20 -14
- package/dist/core/hooks/adapter/output.d.ts.map +1 -0
- package/dist/core/hooks/{harness → adapter}/output.js +30 -22
- package/dist/core/hooks/{harness → adapter}/parse.d.ts +5 -5
- package/dist/core/hooks/{harness → adapter}/parse.d.ts.map +1 -1
- package/dist/core/hooks/{harness → adapter}/parse.js +4 -4
- package/dist/core/hooks/{harness → adapter}/wiring.d.ts +15 -15
- package/dist/core/hooks/{harness → adapter}/wiring.d.ts.map +1 -1
- package/dist/core/hooks/{harness → adapter}/wiring.js +13 -13
- package/dist/core/hooks/cli.d.ts +3 -3
- package/dist/core/hooks/cli.js +175 -151
- package/dist/core/hooks/effects/image-capture.d.ts +5 -5
- package/dist/core/hooks/effects/image-capture.d.ts.map +1 -1
- package/dist/core/hooks/effects/image-capture.js +3 -3
- package/dist/core/hooks/effects/index.d.ts +12 -12
- package/dist/core/hooks/effects/index.d.ts.map +1 -1
- package/dist/core/hooks/effects/index.js +17 -17
- package/dist/core/hooks/events/emit.d.ts +2 -2
- package/dist/core/hooks/events/emit.d.ts.map +1 -1
- package/dist/core/hooks/events/emit.js +1 -1
- package/dist/core/hooks/events/rotate.js +1 -1
- package/dist/core/hooks/events/schema.d.ts +13 -5
- package/dist/core/hooks/events/schema.d.ts.map +1 -1
- package/dist/core/hooks/guard-path.d.ts +2 -2
- package/dist/core/hooks/guard-path.js +2 -2
- package/dist/core/hooks/index.d.ts +1 -1
- package/dist/core/hooks/index.d.ts.map +1 -1
- package/dist/core/hooks/resolve/anchor.d.ts +14 -5
- package/dist/core/hooks/resolve/anchor.d.ts.map +1 -1
- package/dist/core/hooks/resolve/anchor.js +64 -7
- package/dist/core/hooks/resolve/coord-root.d.ts +3 -3
- package/dist/core/hooks/resolve/coord-root.js +4 -4
- package/dist/core/hooks/resolve/intent.js +1 -1
- package/dist/core/hooks/resolve/owner.d.ts +1 -1
- package/dist/core/hooks/resolve/owner.d.ts.map +1 -1
- package/dist/core/hooks/resolve/owner.js +11 -5
- package/dist/core/{scratch → journal}/index.d.ts +25 -25
- package/dist/core/{scratch → journal}/index.d.ts.map +1 -1
- package/dist/core/{scratch → journal}/index.js +38 -38
- package/dist/core/policy/evaluate.d.ts +1 -1
- package/dist/core/policy/evaluate.d.ts.map +1 -1
- package/dist/core/policy/evaluate.js +7 -7
- package/dist/core/policy/types.d.ts +3 -3
- package/dist/core/policy/types.d.ts.map +1 -1
- package/dist/core/work/state.d.ts +12 -4
- package/dist/core/work/state.d.ts.map +1 -1
- package/dist/core/work/state.js +50 -17
- package/dist/core/workflow/adapters.d.ts +17 -0
- package/dist/core/workflow/adapters.d.ts.map +1 -0
- package/dist/core/workflow/adapters.js +21 -0
- package/dist/core/workflow/approvals.js +6 -6
- package/dist/core/workflow/billing.d.ts +9 -9
- package/dist/core/workflow/billing.js +9 -9
- package/dist/core/workflow/child-env.d.ts +10 -2
- package/dist/core/workflow/child-env.d.ts.map +1 -1
- package/dist/core/workflow/child-env.js +12 -4
- package/dist/core/workflow/engine.d.ts +15 -5
- package/dist/core/workflow/engine.d.ts.map +1 -1
- package/dist/core/workflow/engine.js +138 -83
- package/dist/core/workflow/evidence-preflight.d.ts +24 -0
- package/dist/core/workflow/evidence-preflight.d.ts.map +1 -0
- package/dist/core/workflow/evidence-preflight.js +134 -0
- package/dist/core/workflow/index.d.ts +1 -1
- package/dist/core/workflow/index.d.ts.map +1 -1
- package/dist/core/workflow/proof.d.ts +11 -5
- package/dist/core/workflow/proof.d.ts.map +1 -1
- package/dist/core/workflow/proof.js +45 -36
- package/dist/core/workflow/run-state.d.ts +1 -1
- package/dist/core/workflow/run-state.js +7 -7
- package/dist/core/workflow/sandbox-projection.d.ts +6 -6
- package/dist/core/workflow/sandbox-projection.d.ts.map +1 -1
- package/dist/core/workflow/sandbox-projection.js +11 -11
- package/dist/core/workflow/spawn-claude.d.ts +3 -3
- package/dist/core/workflow/spawn-claude.d.ts.map +1 -1
- package/dist/core/workflow/spawn-claude.js +9 -6
- package/dist/core/workflow/spawn-codex.d.ts +4 -4
- package/dist/core/workflow/spawn-codex.d.ts.map +1 -1
- package/dist/core/workflow/spawn-codex.js +9 -6
- package/dist/core/workflow/spawn-cursor.d.ts +4 -4
- package/dist/core/workflow/spawn-cursor.d.ts.map +1 -1
- package/dist/core/workflow/spawn-cursor.js +9 -6
- package/dist/core/workflow/spawn-failure.d.ts +1 -1
- package/dist/core/workflow/spawn-failure.js +1 -1
- package/dist/core/workflow/specialists.js +2 -2
- package/dist/core/workflow/{journal.d.ts → transcript.d.ts} +8 -8
- package/dist/core/workflow/transcript.d.ts.map +1 -0
- package/dist/core/workflow/{journal.js → transcript.js} +14 -14
- package/dist/core/workflow/types.d.ts +98 -51
- package/dist/core/workflow/types.d.ts.map +1 -1
- package/dist/core/workflow/types.js +11 -1
- package/dist/core/workflow/validate.d.ts +1 -1
- package/dist/core/workflow/validate.js +1 -1
- package/dist/core/workflow/workspaces/cancellation.js +2 -2
- package/dist/core/workflow/workspaces/cleanup.js +2 -2
- package/dist/core/workflow/workspaces/execution.js +6 -6
- package/dist/core/workflow/workspaces/inspect.js +9 -9
- package/dist/core/workflow/workspaces/integration.js +14 -14
- package/dist/core/workflow/workspaces/lifecycle.d.ts +1 -1
- package/dist/core/workflow/workspaces/lifecycle.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/lifecycle.js +9 -9
- package/dist/core/workflow/workspaces/local-git.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/local-git.js +39 -5
- package/dist/core/workflow/workspaces/recovery.js +3 -3
- package/dist/core/workflow/workspaces/state.d.ts +1 -1
- package/dist/core/workflow/workspaces/state.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/state.js +3 -3
- package/dist/core/workflow/workspaces/types.d.ts +1 -1
- package/dist/core/workflow/workspaces/types.d.ts.map +1 -1
- package/dist/lib/browser/asserts.d.ts +28 -0
- package/dist/lib/browser/asserts.d.ts.map +1 -0
- package/dist/lib/browser/asserts.js +152 -0
- package/dist/lib/browser/client.d.ts +49 -0
- package/dist/lib/browser/client.d.ts.map +1 -1
- package/dist/lib/browser/client.js +155 -14
- package/dist/lib/browser/content-checks.d.ts +108 -0
- package/dist/lib/browser/content-checks.d.ts.map +1 -0
- package/dist/lib/browser/content-checks.js +474 -0
- package/dist/lib/browser/critique.d.ts +91 -0
- package/dist/lib/browser/critique.d.ts.map +1 -0
- package/dist/lib/browser/critique.js +167 -0
- package/dist/lib/browser/geometry.d.ts +32 -0
- package/dist/lib/browser/geometry.d.ts.map +1 -1
- package/dist/lib/browser/geometry.js +244 -1
- package/dist/lib/browser/index.d.ts +4 -1
- package/dist/lib/browser/index.d.ts.map +1 -1
- package/dist/lib/browser/index.js +2 -0
- package/dist/lib/claude-desktop.d.ts.map +1 -1
- package/dist/lib/claude-desktop.js +4 -1
- package/dist/lib/council/index.d.ts +6 -6
- package/dist/lib/council/index.d.ts.map +1 -1
- package/dist/lib/council/index.js +8 -8
- package/dist/lib/identities/assume.d.ts +2 -2
- package/dist/lib/identities/assume.js +7 -7
- package/dist/lib/instructions/apply.d.ts +3 -3
- package/dist/lib/instructions/apply.d.ts.map +1 -1
- package/dist/lib/instructions/apply.js +75 -27
- package/dist/lib/instructions/host-addendum.d.ts +54 -0
- package/dist/lib/instructions/host-addendum.d.ts.map +1 -0
- package/dist/lib/instructions/host-addendum.js +107 -0
- package/dist/lib/instructions/templates.d.ts +10 -3
- package/dist/lib/instructions/templates.d.ts.map +1 -1
- package/dist/lib/instructions/templates.js +41 -12
- package/dist/lib/machine.d.ts +2 -2
- package/dist/lib/machine.js +2 -2
- package/package.json +26 -21
- package/schemas/config.schema.json +26 -1
- package/src/commander.ts +22 -13
- package/src/commands/{harness.ts → adapter.ts} +46 -46
- package/src/commands/agents.ts +71 -64
- package/src/commands/artifacts.ts +168 -0
- package/src/commands/backup.ts +47 -33
- package/src/commands/browse.ts +438 -2
- package/src/commands/checkpoint.ts +196 -0
- package/src/commands/context.ts +3 -176
- package/src/commands/decision.ts +41 -7
- package/src/commands/deinit.ts +16 -16
- package/src/commands/doctor.ts +28 -28
- package/src/commands/{supervisor.ts → governor.ts} +184 -154
- package/src/commands/init.ts +50 -34
- package/src/commands/{scratch.ts → journal.ts} +60 -60
- package/src/commands/policy.ts +1 -1
- package/src/commands/presence.ts +2 -2
- package/src/commands/sync.ts +3 -3
- package/src/commands/tunnel.ts +237 -34
- package/src/commands/web-fetch.ts +1 -1
- package/src/commands/web.ts +56 -1
- package/src/commands/work.ts +50 -35
- package/src/commands/workflow.ts +43 -34
- package/src/core/{harnesses → adapters}/attest-projection.ts +2 -2
- package/src/core/{harnesses → adapters}/attest.ts +27 -27
- package/src/core/{harnesses → adapters}/attestation.ts +41 -41
- package/src/core/{harnesses → adapters}/bench.ts +34 -43
- package/src/core/adapters/index.ts +56 -0
- package/src/core/{harnesses → adapters}/profiles.ts +16 -16
- package/src/core/{harnesses → adapters}/registry.ts +23 -23
- package/src/core/{harnesses → adapters}/types.ts +23 -23
- package/src/core/agents/canonical-emit.ts +6 -6
- package/src/core/agents/cli-emit.ts +1 -1
- package/src/core/agents/cli.ts +30 -28
- package/src/core/agents/codex-replay.ts +6 -6
- package/src/core/agents/coord-client.ts +119 -28
- package/src/core/agents/events/consume.ts +1 -1
- package/src/core/agents/events/emit.ts +7 -6
- package/src/core/agents/render/prompt-context.ts +107 -31
- package/src/core/agents/render/session-context.ts +6 -6
- package/src/core/agents/rules/claim-conflict.ts +4 -7
- package/src/core/agents/rules/commit-conflict.ts +103 -22
- package/src/core/agents/rules/stop-hook.ts +33 -17
- package/src/core/agents/rules/verdict.ts +13 -0
- package/src/core/agents/session-events.ts +4 -4
- package/src/core/agents/state/council.ts +1 -1
- package/src/core/agents/state/heartbeat-projector.ts +16 -12
- package/src/core/agents/state/heartbeat-writer.ts +12 -12
- package/src/core/agents/state/{scratch.ts → journal.ts} +12 -12
- package/src/core/agents/state/pidmap.ts +130 -15
- package/src/core/agents/state/proc-start.ts +208 -0
- package/src/core/agents/state/shell-mutation.ts +1 -1
- package/src/core/agents/state/stale-sweep.ts +7 -7
- package/src/core/artifacts/index.ts +557 -0
- package/src/core/config.ts +30 -2
- package/src/core/context/index.ts +15 -15
- package/src/core/governor/adapter-spread.ts +82 -0
- package/src/core/governor/index.ts +91 -0
- package/src/core/governor/plan-api.ts +115 -0
- package/src/core/{supervisor → governor}/plan-read.ts +112 -97
- package/src/core/{supervisor → governor}/plan-types.ts +59 -56
- package/src/core/{supervisor → governor}/planning.ts +223 -192
- package/src/core/governor/read.ts +59 -0
- package/src/core/{supervisor → governor}/runner.ts +39 -39
- package/src/core/{supervisor → governor}/service-read.ts +22 -22
- package/src/core/{supervisor → governor}/service.ts +117 -119
- package/src/core/{supervisor → governor}/state.ts +325 -167
- package/src/core/hooks/adapter/detect.ts +30 -0
- package/src/core/hooks/{harness → adapter}/events.ts +16 -14
- package/src/core/hooks/{harness → adapter}/output.ts +31 -23
- package/src/core/hooks/{harness → adapter}/parse.ts +5 -5
- package/src/core/hooks/{harness → adapter}/wiring.ts +21 -21
- package/src/core/hooks/cli.ts +185 -162
- package/src/core/hooks/effects/image-capture.ts +6 -6
- package/src/core/hooks/effects/index.ts +17 -17
- package/src/core/hooks/events/emit.ts +3 -3
- package/src/core/hooks/events/rotate.ts +1 -1
- package/src/core/hooks/events/schema.ts +15 -7
- package/src/core/hooks/guard-path.ts +2 -2
- package/src/core/hooks/index.ts +1 -1
- package/src/core/hooks/resolve/anchor.ts +63 -8
- package/src/core/hooks/resolve/coord-root.ts +4 -4
- package/src/core/hooks/resolve/intent.ts +1 -1
- package/src/core/hooks/resolve/owner.ts +11 -5
- package/src/core/{scratch → journal}/index.ts +53 -53
- package/src/core/policy/evaluate.ts +8 -8
- package/src/core/policy/types.ts +3 -3
- package/src/core/work/state.ts +65 -23
- package/src/core/workflow/adapters.ts +33 -0
- package/src/core/workflow/approvals.ts +7 -7
- package/src/core/workflow/billing.ts +12 -12
- package/src/core/workflow/child-env.ts +13 -4
- package/src/core/workflow/engine.ts +155 -90
- package/src/core/workflow/evidence-preflight.ts +165 -0
- package/src/core/workflow/index.ts +3 -3
- package/src/core/workflow/proof.ts +60 -50
- package/src/core/workflow/run-state.ts +7 -7
- package/src/core/workflow/sandbox-projection.ts +16 -16
- package/src/core/workflow/spawn-claude.ts +13 -10
- package/src/core/workflow/spawn-codex.ts +13 -10
- package/src/core/workflow/spawn-cursor.ts +13 -10
- package/src/core/workflow/spawn-failure.ts +1 -1
- package/src/core/workflow/specialists.ts +2 -2
- package/src/core/workflow/{journal.ts → transcript.ts} +16 -16
- package/src/core/workflow/types.ts +108 -51
- package/src/core/workflow/validate.ts +1 -1
- package/src/core/workflow/workspaces/cancellation.ts +2 -2
- package/src/core/workflow/workspaces/cleanup.ts +2 -2
- package/src/core/workflow/workspaces/execution.ts +6 -6
- package/src/core/workflow/workspaces/inspect.ts +14 -11
- package/src/core/workflow/workspaces/integration.ts +18 -14
- package/src/core/workflow/workspaces/lifecycle.ts +10 -10
- package/src/core/workflow/workspaces/local-git.ts +47 -5
- package/src/core/workflow/workspaces/recovery.ts +3 -3
- package/src/core/workflow/workspaces/state.ts +4 -3
- package/src/core/workflow/workspaces/types.ts +1 -1
- package/src/lib/browser/asserts.ts +177 -0
- package/src/lib/browser/client.ts +189 -14
- package/src/lib/browser/content-checks.ts +571 -0
- package/src/lib/browser/critique.ts +231 -0
- package/src/lib/browser/geometry.ts +297 -1
- package/src/lib/browser/index.ts +34 -0
- package/src/lib/claude-desktop.ts +4 -1
- package/src/lib/council/index.ts +9 -9
- package/src/lib/identities/assume.ts +7 -7
- package/src/lib/instructions/apply.ts +87 -25
- package/src/lib/instructions/host-addendum.ts +126 -0
- package/src/lib/instructions/templates.ts +44 -15
- package/src/lib/machine.ts +2 -2
- package/dist/commands/harness.d.ts +0 -12
- package/dist/commands/harness.d.ts.map +0 -1
- package/dist/commands/scratch.d.ts +0 -4
- package/dist/commands/scratch.d.ts.map +0 -1
- package/dist/commands/session.d.ts.map +0 -1
- package/dist/commands/session.js +0 -162
- package/dist/commands/supervisor.d.ts +0 -4
- package/dist/commands/supervisor.d.ts.map +0 -1
- package/dist/core/harnesses/attest-projection.d.ts.map +0 -1
- package/dist/core/harnesses/attest.d.ts.map +0 -1
- package/dist/core/harnesses/attestation.d.ts.map +0 -1
- package/dist/core/harnesses/bench.d.ts.map +0 -1
- package/dist/core/harnesses/index.d.ts +0 -13
- package/dist/core/harnesses/index.d.ts.map +0 -1
- package/dist/core/harnesses/index.js +0 -6
- package/dist/core/harnesses/profiles.d.ts.map +0 -1
- package/dist/core/harnesses/registry.d.ts +0 -15
- package/dist/core/harnesses/registry.d.ts.map +0 -1
- package/dist/core/harnesses/types.d.ts.map +0 -1
- package/dist/core/hooks/harness/detect.d.ts +0 -9
- package/dist/core/hooks/harness/events.d.ts.map +0 -1
- package/dist/core/hooks/harness/output.d.ts.map +0 -1
- package/dist/core/supervisor/index.d.ts +0 -7
- package/dist/core/supervisor/index.d.ts.map +0 -1
- package/dist/core/supervisor/index.js +0 -6
- package/dist/core/supervisor/plan-api.d.ts +0 -23
- package/dist/core/supervisor/plan-api.d.ts.map +0 -1
- package/dist/core/supervisor/plan-api.js +0 -50
- package/dist/core/supervisor/plan-read.d.ts +0 -5
- package/dist/core/supervisor/plan-read.d.ts.map +0 -1
- package/dist/core/supervisor/plan-types.d.ts.map +0 -1
- package/dist/core/supervisor/planning.d.ts +0 -33
- package/dist/core/supervisor/planning.d.ts.map +0 -1
- package/dist/core/supervisor/read.d.ts +0 -6
- package/dist/core/supervisor/read.d.ts.map +0 -1
- package/dist/core/supervisor/read.js +0 -4
- package/dist/core/supervisor/runner.d.ts +0 -37
- package/dist/core/supervisor/runner.d.ts.map +0 -1
- package/dist/core/supervisor/service-read.d.ts +0 -6
- package/dist/core/supervisor/service-read.d.ts.map +0 -1
- package/dist/core/supervisor/service.d.ts +0 -114
- package/dist/core/supervisor/service.d.ts.map +0 -1
- package/dist/core/supervisor/state.d.ts +0 -95
- package/dist/core/supervisor/state.d.ts.map +0 -1
- package/dist/core/workflow/harnesses.d.ts +0 -17
- package/dist/core/workflow/harnesses.d.ts.map +0 -1
- package/dist/core/workflow/harnesses.js +0 -21
- package/dist/core/workflow/journal.d.ts.map +0 -1
- package/src/commands/session.ts +0 -187
- package/src/core/harnesses/index.ts +0 -56
- package/src/core/hooks/harness/detect.ts +0 -30
- package/src/core/supervisor/index.ts +0 -89
- package/src/core/supervisor/plan-api.ts +0 -73
- package/src/core/supervisor/read.ts +0 -59
- package/src/core/workflow/harnesses.ts +0 -33
- /package/dist/core/{harnesses → adapters}/attest-projection.js +0 -0
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
// Vision-model page critic for `harn browse --check-critique`.
|
|
2
|
+
//
|
|
3
|
+
// Heuristic checks only catch what we can enumerate. The reason a human still
|
|
4
|
+
// eyeballs a page is the long tail — the odd thing that "looks off" without
|
|
5
|
+
// tripping a named rule. This hands a rendered page to a vision model and asks
|
|
6
|
+
// for that judgement, structured.
|
|
7
|
+
//
|
|
8
|
+
// Two things keep it honest:
|
|
9
|
+
// 1. Tiling. A tall page screenshotted whole and downscaled to a model's
|
|
10
|
+
// input budget loses the detail the critique depends on. So the page is
|
|
11
|
+
// cut into overlapping vertical bands (or one tile per selector match),
|
|
12
|
+
// each captured at full resolution and judged on its own, with findings
|
|
13
|
+
// tagged by tile + document scroll offset for locality.
|
|
14
|
+
// 2. Injection. harnery ships no model client and no API key. The host wires
|
|
15
|
+
// a `critiqueProvider` into the program context (same pattern as
|
|
16
|
+
// `extraHeaders`); without one, the check reports `skipped`, never a
|
|
17
|
+
// false pass. That keeps this portable — the tiling, prompt, and
|
|
18
|
+
// orchestration live here; the model call lives in the host.
|
|
19
|
+
|
|
20
|
+
import { PNG } from "pngjs";
|
|
21
|
+
|
|
22
|
+
export interface CritiqueTile {
|
|
23
|
+
index: number;
|
|
24
|
+
/** Section label or "band N", for locating a finding. */
|
|
25
|
+
label: string;
|
|
26
|
+
/** Document-space Y offset of the tile's top, so findings map to a place. */
|
|
27
|
+
scrollY: number;
|
|
28
|
+
width: number;
|
|
29
|
+
height: number;
|
|
30
|
+
/** Base64 PNG of the tile (no data: prefix). */
|
|
31
|
+
pngBase64: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface CritiqueFinding {
|
|
35
|
+
tile: number;
|
|
36
|
+
severity: "high" | "medium" | "low";
|
|
37
|
+
category: string;
|
|
38
|
+
description: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface CritiqueResult {
|
|
42
|
+
rule: "critique";
|
|
43
|
+
/** Number of tiles the page was cut into. */
|
|
44
|
+
tiles: number;
|
|
45
|
+
/** Whether a provider was available. False → outcome "skipped". */
|
|
46
|
+
provider: boolean;
|
|
47
|
+
findings: CritiqueFinding[];
|
|
48
|
+
outcome: "pass" | "fail" | "skipped";
|
|
49
|
+
/** Set when skipped or a provider call failed. */
|
|
50
|
+
error?: string;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* The host-injected model call. Given one tile and the rubric, return the
|
|
55
|
+
* findings for that tile. Throwing is caught and surfaced as an error finding
|
|
56
|
+
* so one bad tile never sinks the run.
|
|
57
|
+
*/
|
|
58
|
+
export type CritiqueProvider = (input: {
|
|
59
|
+
url: string;
|
|
60
|
+
rubric: string;
|
|
61
|
+
tile: CritiqueTile;
|
|
62
|
+
}) => Promise<CritiqueFinding[]>;
|
|
63
|
+
|
|
64
|
+
export const DEFAULT_CRITIQUE_RUBRIC = [
|
|
65
|
+
"You are reviewing one vertical slice of a rendered web page for visual defects.",
|
|
66
|
+
"Report ONLY concrete, visible problems a careful designer would flag:",
|
|
67
|
+
"- text cut off, overlapping, or colliding with other elements",
|
|
68
|
+
"- text that is hard to read against its background (low contrast)",
|
|
69
|
+
"- misaligned or unevenly spaced elements; two blocks touching with no gap",
|
|
70
|
+
"- broken, distorted, or missing images",
|
|
71
|
+
"- leaked template tokens, placeholder text, or obviously wrong values",
|
|
72
|
+
"- anything that looks broken, unfinished, or accidental",
|
|
73
|
+
"Do NOT comment on content quality, wording, tone, or subjective taste.",
|
|
74
|
+
"If the slice looks fine, return an empty list. Be precise and terse.",
|
|
75
|
+
"Return JSON only: an array of objects",
|
|
76
|
+
'{"severity":"high|medium|low","category":"<short-slug>","description":"<one sentence>"}.',
|
|
77
|
+
].join("\n");
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Cut a page of height `pageHeight` into overlapping vertical bands. Pure math
|
|
81
|
+
* so the caller (which owns the page) can screenshot each `clip` rect. The
|
|
82
|
+
* overlap keeps a finding that straddles a seam visible in at least one tile.
|
|
83
|
+
*/
|
|
84
|
+
export function bandRects(
|
|
85
|
+
pageHeight: number,
|
|
86
|
+
pageWidth: number,
|
|
87
|
+
bandHeight: number,
|
|
88
|
+
overlap: number,
|
|
89
|
+
): Array<{ index: number; x: number; y: number; width: number; height: number }> {
|
|
90
|
+
const bands: Array<{ index: number; x: number; y: number; width: number; height: number }> = [];
|
|
91
|
+
if (pageHeight <= 0 || pageWidth <= 0) return bands;
|
|
92
|
+
const step = Math.max(1, bandHeight - overlap);
|
|
93
|
+
let index = 0;
|
|
94
|
+
for (let y = 0; y < pageHeight; y += step) {
|
|
95
|
+
const height = Math.min(bandHeight, pageHeight - y);
|
|
96
|
+
bands.push({ index, x: 0, y, width: pageWidth, height });
|
|
97
|
+
index++;
|
|
98
|
+
if (y + height >= pageHeight) break;
|
|
99
|
+
}
|
|
100
|
+
return bands;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Cut tiles out of ONE full-page screenshot with pngjs. Playwright's own
|
|
105
|
+
* `clip` is viewport-relative, so a band below the fold errors with "clipped
|
|
106
|
+
* area outside the resulting image" — capturing the whole page once and
|
|
107
|
+
* cropping in pixel space sidesteps that and keeps every tile at full
|
|
108
|
+
* resolution. Pass `elementRects` for semantic (per-element) tiling; otherwise
|
|
109
|
+
* the page is banded from the actual image height. Rects are clamped to the
|
|
110
|
+
* image so an off-by-one never throws.
|
|
111
|
+
*/
|
|
112
|
+
export function tilesFromFullPage(
|
|
113
|
+
fullPageBuffer: Buffer,
|
|
114
|
+
opts: {
|
|
115
|
+
elementRects?: Array<{ label: string; x: number; y: number; width: number; height: number }>;
|
|
116
|
+
bandHeight?: number;
|
|
117
|
+
overlap?: number;
|
|
118
|
+
maxTiles: number;
|
|
119
|
+
},
|
|
120
|
+
): CritiqueTile[] {
|
|
121
|
+
const png = PNG.sync.read(fullPageBuffer);
|
|
122
|
+
const rects =
|
|
123
|
+
opts.elementRects && opts.elementRects.length > 0
|
|
124
|
+
? opts.elementRects.map((r, index) => ({ index, ...r }))
|
|
125
|
+
: bandRects(png.height, png.width, opts.bandHeight ?? 1400, opts.overlap ?? 120).map((b) => ({
|
|
126
|
+
index: b.index,
|
|
127
|
+
label: `band ${b.index + 1}`,
|
|
128
|
+
x: b.x,
|
|
129
|
+
y: b.y,
|
|
130
|
+
width: b.width,
|
|
131
|
+
height: b.height,
|
|
132
|
+
}));
|
|
133
|
+
const tiles: CritiqueTile[] = [];
|
|
134
|
+
for (const rect of rects.slice(0, opts.maxTiles)) {
|
|
135
|
+
const sx = Math.max(0, Math.min(Math.round(rect.x), png.width - 1));
|
|
136
|
+
const sy = Math.max(0, Math.min(Math.round(rect.y), png.height - 1));
|
|
137
|
+
const w = Math.max(1, Math.min(Math.round(rect.width), png.width - sx));
|
|
138
|
+
const h = Math.max(1, Math.min(Math.round(rect.height), png.height - sy));
|
|
139
|
+
const dst = new PNG({ width: w, height: h });
|
|
140
|
+
// Manual RGBA row copy — pngjs's bitblt isn't available under every runtime
|
|
141
|
+
// (missing under Bun's build), so copy the crop window row by row instead.
|
|
142
|
+
for (let row = 0; row < h; row++) {
|
|
143
|
+
const srcStart = ((sy + row) * png.width + sx) * 4;
|
|
144
|
+
const dstStart = row * w * 4;
|
|
145
|
+
png.data.copy(dst.data, dstStart, srcStart, srcStart + w * 4);
|
|
146
|
+
}
|
|
147
|
+
tiles.push({
|
|
148
|
+
index: rect.index,
|
|
149
|
+
label: rect.label,
|
|
150
|
+
scrollY: sy,
|
|
151
|
+
width: w,
|
|
152
|
+
height: h,
|
|
153
|
+
pngBase64: PNG.sync.write(dst).toString("base64"),
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
return tiles;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Validate + normalize a provider's raw output into CritiqueFindings. Tolerant
|
|
161
|
+
* of a model returning a bare array or wrapping it in `{ findings: [...] }`,
|
|
162
|
+
* and drops entries that are not shaped like a finding.
|
|
163
|
+
*/
|
|
164
|
+
export function normalizeFindings(raw: unknown, tile: number): CritiqueFinding[] {
|
|
165
|
+
const arr = Array.isArray(raw)
|
|
166
|
+
? raw
|
|
167
|
+
: raw && typeof raw === "object" && Array.isArray((raw as { findings?: unknown }).findings)
|
|
168
|
+
? (raw as { findings: unknown[] }).findings
|
|
169
|
+
: [];
|
|
170
|
+
const severities = new Set(["high", "medium", "low"]);
|
|
171
|
+
const out: CritiqueFinding[] = [];
|
|
172
|
+
for (const item of arr) {
|
|
173
|
+
if (!item || typeof item !== "object") continue;
|
|
174
|
+
const record = item as Record<string, unknown>;
|
|
175
|
+
const severity = String(record.severity ?? "low").toLowerCase();
|
|
176
|
+
const description = typeof record.description === "string" ? record.description.trim() : "";
|
|
177
|
+
if (!description) continue;
|
|
178
|
+
out.push({
|
|
179
|
+
tile,
|
|
180
|
+
severity: severities.has(severity) ? (severity as CritiqueFinding["severity"]) : "low",
|
|
181
|
+
category:
|
|
182
|
+
typeof record.category === "string" && record.category.trim()
|
|
183
|
+
? record.category.trim()
|
|
184
|
+
: "visual",
|
|
185
|
+
description,
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
return out;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Run the critique over pre-captured tiles with an injected provider. Findings
|
|
193
|
+
* are collected across tiles; a provider throw becomes a `high` error finding
|
|
194
|
+
* for that tile rather than aborting. Outcome is `fail` when any `high` finding
|
|
195
|
+
* survives (the gate escalation lives in the caller).
|
|
196
|
+
*/
|
|
197
|
+
export async function runCritique(args: {
|
|
198
|
+
url: string;
|
|
199
|
+
rubric: string;
|
|
200
|
+
tiles: CritiqueTile[];
|
|
201
|
+
provider: CritiqueProvider | undefined;
|
|
202
|
+
}): Promise<CritiqueResult> {
|
|
203
|
+
const { url, rubric, tiles, provider } = args;
|
|
204
|
+
if (!provider) {
|
|
205
|
+
return {
|
|
206
|
+
rule: "critique",
|
|
207
|
+
tiles: tiles.length,
|
|
208
|
+
provider: false,
|
|
209
|
+
findings: [],
|
|
210
|
+
outcome: "skipped",
|
|
211
|
+
error:
|
|
212
|
+
"no critiqueProvider injected by the host (see HarneryProgramContext.critiqueProvider)",
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
const findings: CritiqueFinding[] = [];
|
|
216
|
+
for (const tile of tiles) {
|
|
217
|
+
try {
|
|
218
|
+
const tileFindings = await provider({ url, rubric, tile });
|
|
219
|
+
findings.push(...tileFindings.map((f) => ({ ...f, tile: tile.index })));
|
|
220
|
+
} catch (err: unknown) {
|
|
221
|
+
findings.push({
|
|
222
|
+
tile: tile.index,
|
|
223
|
+
severity: "high",
|
|
224
|
+
category: "provider-error",
|
|
225
|
+
description: `critique provider failed on ${tile.label}: ${err instanceof Error ? err.message : String(err)}`,
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
const outcome = findings.some((f) => f.severity === "high") ? "fail" : "pass";
|
|
230
|
+
return { rule: "critique", tiles: tiles.length, provider: true, findings, outcome };
|
|
231
|
+
}
|
|
@@ -115,6 +115,35 @@ export interface OverlapResult {
|
|
|
115
115
|
truncated: boolean;
|
|
116
116
|
}
|
|
117
117
|
|
|
118
|
+
export interface CrowdPair {
|
|
119
|
+
before: LayoutElementMeasurement;
|
|
120
|
+
after: LayoutElementMeasurement;
|
|
121
|
+
/** Edge separation along `axis` in CSS px; negative means the panels overlap. */
|
|
122
|
+
separationPx: number;
|
|
123
|
+
/** "y" = stacked, "x" = side by side, "overlap" = they intersect in 2D. */
|
|
124
|
+
axis: "x" | "y" | "overlap";
|
|
125
|
+
minGapPx: number;
|
|
126
|
+
/** DOM label of the shared parent, so the fix location is obvious. */
|
|
127
|
+
parentLabel: string;
|
|
128
|
+
/**
|
|
129
|
+
* Whether each consecutive sibling peer was a leaf panel or a wrapper that
|
|
130
|
+
* contains panels. `before`/`after` always measure the nearest face panels
|
|
131
|
+
* (a descendant when the peer is composite).
|
|
132
|
+
*/
|
|
133
|
+
beforeKind: "panel" | "composite";
|
|
134
|
+
afterKind: "panel" | "composite";
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export interface CrowdResult {
|
|
138
|
+
rule: "crowd";
|
|
139
|
+
selector: string;
|
|
140
|
+
found: boolean;
|
|
141
|
+
outcome: LayoutOutcome;
|
|
142
|
+
minGapPx: number;
|
|
143
|
+
issues: CrowdPair[];
|
|
144
|
+
truncated: boolean;
|
|
145
|
+
}
|
|
146
|
+
|
|
118
147
|
export interface LayoutLintRequest {
|
|
119
148
|
align: Array<{ selector: string; axis: LayoutAxis; tolerancePx: number }>;
|
|
120
149
|
gap: Array<{
|
|
@@ -125,6 +154,7 @@ export interface LayoutLintRequest {
|
|
|
125
154
|
}>;
|
|
126
155
|
clip: Array<{ selector: string; tolerancePx: number }>;
|
|
127
156
|
overlap: Array<{ selector: string; tolerancePx: number }>;
|
|
157
|
+
crowd: Array<{ selector: string; minGapPx: number }>;
|
|
128
158
|
}
|
|
129
159
|
|
|
130
160
|
export interface LayoutLintResult {
|
|
@@ -132,6 +162,7 @@ export interface LayoutLintResult {
|
|
|
132
162
|
gap: GapResult[];
|
|
133
163
|
clip: ClipResult[];
|
|
134
164
|
overlap: OverlapResult[];
|
|
165
|
+
crowd: CrowdResult[];
|
|
135
166
|
}
|
|
136
167
|
|
|
137
168
|
export function median(values: number[]): number {
|
|
@@ -631,7 +662,241 @@ export function buildLayoutLintCheck(): (request: LayoutLintRequest) => LayoutLi
|
|
|
631
662
|
};
|
|
632
663
|
});
|
|
633
664
|
|
|
634
|
-
|
|
665
|
+
// A "panel" here means a card-like callout — a block that reads as its own
|
|
666
|
+
// standalone box: a full (4-side) border, a modest corner radius, or a
|
|
667
|
+
// box-shadow. Two flush cards read as one merged block, which `overlap`
|
|
668
|
+
// (needs a 2D intersection) and `gap` (flags uneven spacing, so a
|
|
669
|
+
// uniformly-flush stack passes) both miss.
|
|
670
|
+
//
|
|
671
|
+
// Crowd peers are also *composites*: in-flow siblings that are not panels
|
|
672
|
+
// themselves but contain at least one panel descendant (a card grid, a
|
|
673
|
+
// flow of cards, etc.). Comparing only leaf panels missed the common case
|
|
674
|
+
// where a wrapper-of-cards sits flush against the next card. Separation is
|
|
675
|
+
// measured between the nearest face panels inside each peer, not the
|
|
676
|
+
// wrapper boxes, so a tall section with a card near the top and prose
|
|
677
|
+
// below does not false-fail against the next sibling.
|
|
678
|
+
//
|
|
679
|
+
// Deliberately NOT flagged: structural elements that are flush BY DESIGN —
|
|
680
|
+
// table cells, list/definition rows, and divided segments (a bg-only cell
|
|
681
|
+
// in a `gap:1px` strip). Those have no card boundary of their own, so the
|
|
682
|
+
// test below excludes them without a special-case allowlist. Pills, chips,
|
|
683
|
+
// and badges (radius ≥ half their smaller side) are inline controls, not
|
|
684
|
+
// layout cards, and are excluded too. Plain blocks with no panel chrome
|
|
685
|
+
// and no panel descendants stay out of the peer list.
|
|
686
|
+
const PARENT_LIMIT = 2000;
|
|
687
|
+
const PANEL_DESCENDANT_LIMIT = 100;
|
|
688
|
+
const OVERLAP_EPS = 1;
|
|
689
|
+
const STRUCTURAL_TAGS = new Set([
|
|
690
|
+
"TD",
|
|
691
|
+
"TH",
|
|
692
|
+
"TR",
|
|
693
|
+
"THEAD",
|
|
694
|
+
"TBODY",
|
|
695
|
+
"TFOOT",
|
|
696
|
+
"COL",
|
|
697
|
+
"COLGROUP",
|
|
698
|
+
]);
|
|
699
|
+
|
|
700
|
+
const maxCornerRadiusPx = (style: CSSStyleDeclaration, minDim: number): number => {
|
|
701
|
+
const corners = [
|
|
702
|
+
"border-top-left-radius",
|
|
703
|
+
"border-top-right-radius",
|
|
704
|
+
"border-bottom-right-radius",
|
|
705
|
+
"border-bottom-left-radius",
|
|
706
|
+
];
|
|
707
|
+
let max = 0;
|
|
708
|
+
for (const corner of corners) {
|
|
709
|
+
const token = (style.getPropertyValue(corner) || "").trim().split(/\s+/)[0] ?? "";
|
|
710
|
+
const value = token.endsWith("%")
|
|
711
|
+
? (Number.parseFloat(token) / 100) * minDim
|
|
712
|
+
: Number.parseFloat(token) || 0;
|
|
713
|
+
if (value > max) max = value;
|
|
714
|
+
}
|
|
715
|
+
return max;
|
|
716
|
+
};
|
|
717
|
+
|
|
718
|
+
const isPanel = (element: Element): boolean => {
|
|
719
|
+
if (STRUCTURAL_TAGS.has(element.tagName)) return false;
|
|
720
|
+
const style = getComputedStyle(element);
|
|
721
|
+
if (style.display.startsWith("table") || style.display.startsWith("inline")) return false;
|
|
722
|
+
if (style.boxShadow && style.boxShadow !== "none") return true;
|
|
723
|
+
const sides = ["top", "right", "bottom", "left"];
|
|
724
|
+
const fullBorder = sides.every((side) => {
|
|
725
|
+
const width = Number.parseFloat(style.getPropertyValue(`border-${side}-width`)) || 0;
|
|
726
|
+
const lineStyle = style.getPropertyValue(`border-${side}-style`);
|
|
727
|
+
return width > 0 && lineStyle !== "none" && lineStyle !== "";
|
|
728
|
+
});
|
|
729
|
+
if (fullBorder) return true;
|
|
730
|
+
const rect = element.getBoundingClientRect();
|
|
731
|
+
const minDim = Math.min(rect.width, rect.height);
|
|
732
|
+
const radius = maxCornerRadiusPx(style, minDim);
|
|
733
|
+
// A card has a modest radius; a pill/circle (radius ≥ half its short side)
|
|
734
|
+
// is a control, not a card.
|
|
735
|
+
return radius >= 3 && radius < minDim / 2 - 1;
|
|
736
|
+
};
|
|
737
|
+
|
|
738
|
+
const isInFlowBox = (element: Element): boolean => {
|
|
739
|
+
if (isHidden(element)) return false;
|
|
740
|
+
const style = getComputedStyle(element);
|
|
741
|
+
if (style.position === "absolute" || style.position === "fixed") return false;
|
|
742
|
+
const rect = element.getBoundingClientRect();
|
|
743
|
+
return rect.width > 0 && rect.height > 0;
|
|
744
|
+
};
|
|
745
|
+
|
|
746
|
+
/** Leaf panel faces that represent a crowd peer (self, or descendants). */
|
|
747
|
+
const collectPanelFaces = (
|
|
748
|
+
element: Element,
|
|
749
|
+
selfIsPanel: boolean,
|
|
750
|
+
): LayoutElementMeasurement[] => {
|
|
751
|
+
if (selfIsPanel) return [measure(element, 0, false)];
|
|
752
|
+
const faces: LayoutElementMeasurement[] = [];
|
|
753
|
+
const descendants = element.querySelectorAll("*");
|
|
754
|
+
for (let i = 0; i < descendants.length && faces.length < PANEL_DESCENDANT_LIMIT; i++) {
|
|
755
|
+
const child = descendants[i];
|
|
756
|
+
if (!child || !isInFlowBox(child) || !isPanel(child)) continue;
|
|
757
|
+
faces.push(measure(child, faces.length, false));
|
|
758
|
+
}
|
|
759
|
+
return faces;
|
|
760
|
+
};
|
|
761
|
+
|
|
762
|
+
// In-flow, visible, non-zero-area direct children that are crowd peers:
|
|
763
|
+
// leaf panels, or composites that wrap at least one panel. Non-peer
|
|
764
|
+
// siblings (plain prose, arrows, spacers) drop out of the list so two
|
|
765
|
+
// panels separated only by chrome still get a face-to-face check.
|
|
766
|
+
const collectCrowdPeers = (
|
|
767
|
+
parent: Element,
|
|
768
|
+
): Array<{
|
|
769
|
+
kind: "panel" | "composite";
|
|
770
|
+
faces: LayoutElementMeasurement[];
|
|
771
|
+
}> => {
|
|
772
|
+
const out: Array<{
|
|
773
|
+
kind: "panel" | "composite";
|
|
774
|
+
faces: LayoutElementMeasurement[];
|
|
775
|
+
}> = [];
|
|
776
|
+
const children = [...parent.children];
|
|
777
|
+
for (let index = 0; index < children.length && index < CHILD_LIMIT; index++) {
|
|
778
|
+
const element = children[index];
|
|
779
|
+
if (!element || !isInFlowBox(element)) continue;
|
|
780
|
+
const selfIsPanel = isPanel(element);
|
|
781
|
+
const faces = collectPanelFaces(element, selfIsPanel);
|
|
782
|
+
if (faces.length === 0) continue;
|
|
783
|
+
out.push({
|
|
784
|
+
kind: selfIsPanel ? "panel" : "composite",
|
|
785
|
+
faces,
|
|
786
|
+
});
|
|
787
|
+
}
|
|
788
|
+
return out;
|
|
789
|
+
};
|
|
790
|
+
|
|
791
|
+
const separationOf = (
|
|
792
|
+
a: LayoutElementMeasurement,
|
|
793
|
+
b: LayoutElementMeasurement,
|
|
794
|
+
): { axis: "x" | "y" | "overlap" | "none"; sep: number } => {
|
|
795
|
+
const ra = a.rect;
|
|
796
|
+
const rb = b.rect;
|
|
797
|
+
const yOverlap = Math.min(ra.bottom, rb.bottom) - Math.max(ra.top, rb.top);
|
|
798
|
+
const xOverlap = Math.min(ra.right, rb.right) - Math.max(ra.left, rb.left);
|
|
799
|
+
const sharesY = yOverlap > OVERLAP_EPS;
|
|
800
|
+
const sharesX = xOverlap > OVERLAP_EPS;
|
|
801
|
+
if (sharesX && sharesY) return { axis: "overlap", sep: -Math.min(xOverlap, yOverlap) };
|
|
802
|
+
if (sharesX) {
|
|
803
|
+
const [upper, lower] = ra.top <= rb.top ? [ra, rb] : [rb, ra];
|
|
804
|
+
return { axis: "y", sep: lower.top - upper.bottom };
|
|
805
|
+
}
|
|
806
|
+
if (sharesY) {
|
|
807
|
+
const [left, right] = ra.left <= rb.left ? [ra, rb] : [rb, ra];
|
|
808
|
+
return { axis: "x", sep: right.left - left.right };
|
|
809
|
+
}
|
|
810
|
+
// Diagonal (e.g. a grid row wrap): not edge-adjacent, so never crowded.
|
|
811
|
+
const vGap = Math.max(ra.top, rb.top) - Math.min(ra.bottom, rb.bottom);
|
|
812
|
+
const hGap = Math.max(ra.left, rb.left) - Math.min(ra.right, rb.right);
|
|
813
|
+
return { axis: "none", sep: Math.max(vGap, hGap) };
|
|
814
|
+
};
|
|
815
|
+
|
|
816
|
+
/** Tightest edge-adjacent face pair across two peers' panel faces. */
|
|
817
|
+
const nearestCrowdPair = (
|
|
818
|
+
beforeFaces: LayoutElementMeasurement[],
|
|
819
|
+
afterFaces: LayoutElementMeasurement[],
|
|
820
|
+
): {
|
|
821
|
+
before: LayoutElementMeasurement;
|
|
822
|
+
after: LayoutElementMeasurement;
|
|
823
|
+
axis: "x" | "y" | "overlap";
|
|
824
|
+
sep: number;
|
|
825
|
+
} | null => {
|
|
826
|
+
let best: {
|
|
827
|
+
before: LayoutElementMeasurement;
|
|
828
|
+
after: LayoutElementMeasurement;
|
|
829
|
+
axis: "x" | "y" | "overlap";
|
|
830
|
+
sep: number;
|
|
831
|
+
} | null = null;
|
|
832
|
+
for (const before of beforeFaces) {
|
|
833
|
+
for (const after of afterFaces) {
|
|
834
|
+
const { axis, sep } = separationOf(before, after);
|
|
835
|
+
if (axis === "none") continue;
|
|
836
|
+
if (best === null || sep < best.sep) {
|
|
837
|
+
best = { before, after, axis, sep };
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
return best;
|
|
842
|
+
};
|
|
843
|
+
|
|
844
|
+
const crowd = request.crowd.map(({ selector, minGapPx }): CrowdResult => {
|
|
845
|
+
const container = document.querySelector(selector);
|
|
846
|
+
if (!(container instanceof Element)) {
|
|
847
|
+
return {
|
|
848
|
+
rule: "crowd" as const,
|
|
849
|
+
selector,
|
|
850
|
+
found: false,
|
|
851
|
+
outcome: "fail" as const,
|
|
852
|
+
minGapPx,
|
|
853
|
+
issues: [],
|
|
854
|
+
truncated: false,
|
|
855
|
+
};
|
|
856
|
+
}
|
|
857
|
+
const parents: Element[] = [container, ...container.querySelectorAll("*")];
|
|
858
|
+
const issues: CrowdPair[] = [];
|
|
859
|
+
let truncated = parents.length > PARENT_LIMIT;
|
|
860
|
+
const limit = Math.min(parents.length, PARENT_LIMIT);
|
|
861
|
+
for (let p = 0; p < limit; p++) {
|
|
862
|
+
const parent = parents[p]!;
|
|
863
|
+
const peers = collectCrowdPeers(parent);
|
|
864
|
+
for (let i = 1; i < peers.length; i++) {
|
|
865
|
+
const beforePeer = peers[i - 1]!;
|
|
866
|
+
const afterPeer = peers[i]!;
|
|
867
|
+
const best = nearestCrowdPair(beforePeer.faces, afterPeer.faces);
|
|
868
|
+
if (!best) continue;
|
|
869
|
+
if (best.sep < minGapPx) {
|
|
870
|
+
issues.push({
|
|
871
|
+
before: best.before,
|
|
872
|
+
after: best.after,
|
|
873
|
+
separationPx: best.sep,
|
|
874
|
+
axis: best.axis,
|
|
875
|
+
minGapPx,
|
|
876
|
+
parentLabel: labelOf(parent),
|
|
877
|
+
beforeKind: beforePeer.kind,
|
|
878
|
+
afterKind: afterPeer.kind,
|
|
879
|
+
});
|
|
880
|
+
if (issues.length >= ISSUE_LIMIT) {
|
|
881
|
+
truncated = true;
|
|
882
|
+
break;
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
if (issues.length >= ISSUE_LIMIT) break;
|
|
887
|
+
}
|
|
888
|
+
return {
|
|
889
|
+
rule: "crowd" as const,
|
|
890
|
+
selector,
|
|
891
|
+
found: true,
|
|
892
|
+
outcome: issues.length > 0 ? ("fail" as const) : ("pass" as const),
|
|
893
|
+
minGapPx,
|
|
894
|
+
issues,
|
|
895
|
+
truncated,
|
|
896
|
+
};
|
|
897
|
+
});
|
|
898
|
+
|
|
899
|
+
return { align, gap, clip, overlap, crowd };
|
|
635
900
|
};
|
|
636
901
|
}
|
|
637
902
|
|
|
@@ -685,6 +950,37 @@ export function buildLayoutLintAnnotateScript(): (result: LayoutLintResult) => v
|
|
|
685
950
|
box(issue.intersection, "#e11d48", `overlap ${issue.areaPx.toFixed(0)}px²`);
|
|
686
951
|
}
|
|
687
952
|
}
|
|
953
|
+
for (const check of result.crowd) {
|
|
954
|
+
for (const issue of check.issues) {
|
|
955
|
+
// Draw the touching seam: a thin strip along the edge the two panels
|
|
956
|
+
// share, so "no space here" is visible even at zero separation.
|
|
957
|
+
const a = issue.before.rect;
|
|
958
|
+
const b = issue.after.rect;
|
|
959
|
+
const seam: LayoutRect =
|
|
960
|
+
issue.axis === "x"
|
|
961
|
+
? {
|
|
962
|
+
x: Math.min(a.right, b.right),
|
|
963
|
+
y: Math.max(a.top, b.top),
|
|
964
|
+
width: Math.max(2, Math.abs(issue.separationPx)),
|
|
965
|
+
height: Math.max(1, Math.min(a.bottom, b.bottom) - Math.max(a.top, b.top)),
|
|
966
|
+
top: Math.max(a.top, b.top),
|
|
967
|
+
right: 0,
|
|
968
|
+
bottom: 0,
|
|
969
|
+
left: Math.min(a.right, b.right),
|
|
970
|
+
}
|
|
971
|
+
: {
|
|
972
|
+
x: Math.max(a.left, b.left),
|
|
973
|
+
y: Math.min(a.bottom, b.bottom),
|
|
974
|
+
width: Math.max(1, Math.min(a.right, b.right) - Math.max(a.left, b.left)),
|
|
975
|
+
height: Math.max(2, Math.abs(issue.separationPx)),
|
|
976
|
+
top: Math.min(a.bottom, b.bottom),
|
|
977
|
+
right: 0,
|
|
978
|
+
bottom: 0,
|
|
979
|
+
left: Math.max(a.left, b.left),
|
|
980
|
+
};
|
|
981
|
+
box(seam, "#14b8a6", `crowd ${issue.separationPx.toFixed(1)}px`);
|
|
982
|
+
}
|
|
983
|
+
}
|
|
688
984
|
};
|
|
689
985
|
}
|
|
690
986
|
|
package/src/lib/browser/index.ts
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
export {
|
|
2
|
+
type AssertOp,
|
|
3
|
+
type AssertResult,
|
|
4
|
+
type AssertSpec,
|
|
5
|
+
buildAssertCheck,
|
|
6
|
+
parseAssertSpec,
|
|
7
|
+
} from "./asserts.js";
|
|
1
8
|
export {
|
|
2
9
|
Browser,
|
|
3
10
|
type BrowserOptions,
|
|
@@ -7,6 +14,31 @@ export {
|
|
|
7
14
|
type NavigateResult,
|
|
8
15
|
type PageErrorEvent,
|
|
9
16
|
} from "./client.js";
|
|
17
|
+
export type {
|
|
18
|
+
CheckRect,
|
|
19
|
+
ContentAnnotationBox,
|
|
20
|
+
ContentChecksRequest,
|
|
21
|
+
ContentChecksResult,
|
|
22
|
+
ContrastHit,
|
|
23
|
+
ContrastResult,
|
|
24
|
+
ImageHealthResult,
|
|
25
|
+
ImageHit,
|
|
26
|
+
PlaceholderHit,
|
|
27
|
+
PlaceholderResult,
|
|
28
|
+
TruncationHit,
|
|
29
|
+
TruncationResult,
|
|
30
|
+
} from "./content-checks.js";
|
|
31
|
+
export {
|
|
32
|
+
bandRects,
|
|
33
|
+
type CritiqueFinding,
|
|
34
|
+
type CritiqueProvider,
|
|
35
|
+
type CritiqueResult,
|
|
36
|
+
type CritiqueTile,
|
|
37
|
+
DEFAULT_CRITIQUE_RUBRIC,
|
|
38
|
+
normalizeFindings,
|
|
39
|
+
runCritique,
|
|
40
|
+
tilesFromFullPage,
|
|
41
|
+
} from "./critique.js";
|
|
10
42
|
export {
|
|
11
43
|
captureDevOverlay,
|
|
12
44
|
type DevOverlayError,
|
|
@@ -18,6 +50,8 @@ export type {
|
|
|
18
50
|
AlignResult,
|
|
19
51
|
ClipIssue,
|
|
20
52
|
ClipResult,
|
|
53
|
+
CrowdPair,
|
|
54
|
+
CrowdResult,
|
|
21
55
|
GapCluster,
|
|
22
56
|
GapPair,
|
|
23
57
|
GapResult,
|
|
@@ -226,7 +226,10 @@ export interface MirrorOptions {
|
|
|
226
226
|
|
|
227
227
|
export function entryMatchesSelector(entry: DesktopSessionEntry, selector: string): boolean {
|
|
228
228
|
if (entry.cliSessionId === selector || entry.sessionId === selector) return true;
|
|
229
|
-
|
|
229
|
+
// Optional chain rather than a null guard: the reader normalises a missing
|
|
230
|
+
// title to null, but this is exported, and a hand-built entry with an absent
|
|
231
|
+
// title would slip past `!== null` and then throw on the method call.
|
|
232
|
+
return entry.title?.toLowerCase().includes(selector.toLowerCase()) ?? false;
|
|
230
233
|
}
|
|
231
234
|
|
|
232
235
|
/**
|