@reticlehq/server 3.8.2 → 3.10.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/LICENSE +201 -104
- package/NOTICE +5 -0
- package/README.md +1 -1
- package/SKILL.md +13 -12
- package/bin/launch.js +13 -0
- package/bin/package.json +3 -0
- package/bin/reticle.js +66 -8
- package/dist/chromium-launch-options.d.ts +25 -0
- package/dist/chromium-launch-options.js +21 -1
- package/dist/command/cli/adhoc-suite.js +2 -0
- package/dist/command/cli/adhoc-verdict.d.ts +2 -43
- package/dist/command/cli/adhoc-verdict.js +50 -160
- package/dist/command/cli/answers/gate-exit.d.ts +6 -2
- package/dist/command/cli/answers/gate-exit.js +12 -2
- package/dist/command/cli/answers/mcp-connection-hint.d.ts +2 -0
- package/dist/command/cli/answers/mcp-connection-hint.js +6 -0
- package/dist/command/cli/ci-hint.d.ts +44 -0
- package/dist/command/cli/ci-hint.js +72 -0
- package/dist/command/cli/cli-doctor.d.ts +6 -1
- package/dist/command/cli/cli-doctor.js +46 -5
- package/dist/command/cli/cli-flow-commands.d.ts +7 -1
- package/dist/command/cli/cli-flow-commands.js +39 -24
- package/dist/command/cli/cli-kill.d.ts +21 -0
- package/dist/command/cli/cli-kill.js +47 -4
- package/dist/command/cli/cli-parse-compare.d.ts +22 -0
- package/dist/command/cli/cli-parse-compare.js +75 -0
- package/dist/command/cli/cli-parse-grade.d.ts +36 -0
- package/dist/command/cli/cli-parse-grade.js +75 -0
- package/dist/command/cli/cli-parse-grammar.d.ts +2 -0
- package/dist/command/cli/cli-parse-grammar.js +18 -20
- package/dist/command/cli/cli-parse-verify.d.ts +10 -0
- package/dist/command/cli/cli-parse-verify.js +63 -14
- package/dist/command/cli/cli-parse.d.ts +22 -17
- package/dist/command/cli/cli-parse.js +54 -32
- package/dist/command/cli/cli-update-commands.d.ts +1 -1
- package/dist/command/cli/cli-update-commands.js +29 -20
- package/dist/command/cli/cli-usage.js +61 -31
- package/dist/command/cli/cli-verify.d.ts +17 -3
- package/dist/command/cli/cli-verify.js +59 -21
- package/dist/command/cli/cloud-cli.js +2 -119
- package/dist/command/cli/daemon-link/daemon-client.d.ts +45 -0
- package/dist/command/cli/daemon-link/daemon-client.js +205 -0
- package/dist/command/cli/doctor/browser/chromium-hint.d.ts +6 -1
- package/dist/command/cli/doctor/browser/chromium-hint.js +11 -5
- package/dist/command/cli/doctor/doctor-daemon-line.js +6 -3
- package/dist/command/cli/doctor/doctor-fix.d.ts +44 -0
- package/dist/command/cli/doctor/doctor-fix.js +89 -0
- package/dist/command/cli/doctor/doctor-launcher-line.d.ts +23 -0
- package/dist/command/cli/doctor/doctor-launcher-line.js +60 -0
- package/dist/command/cli/doctor/doctor-rows.d.ts +4 -0
- package/dist/command/cli/doctor/doctor-rows.js +4 -0
- package/dist/command/cli/doctor-repairs.d.ts +64 -0
- package/dist/command/cli/doctor-repairs.js +214 -0
- package/dist/command/cli/drive/drive-command.js +3 -7
- package/dist/command/cli/ensure-daemon.d.ts +26 -0
- package/dist/command/cli/ensure-daemon.js +57 -0
- package/dist/command/cli/finished-run/grade-command.d.ts +23 -0
- package/dist/command/cli/finished-run/grade-command.js +180 -0
- package/dist/command/cli/first-use-checks.d.ts +41 -0
- package/dist/command/cli/first-use-checks.js +127 -0
- package/dist/command/cli/first-verdict-node.d.ts +3 -0
- package/dist/command/cli/first-verdict-node.js +189 -0
- package/dist/command/cli/first-verdict.d.ts +126 -0
- package/dist/command/cli/first-verdict.js +267 -0
- package/dist/command/cli/launch/sdk-version-match.d.ts +3 -2
- package/dist/command/cli/launch/sdk-version-match.js +3 -2
- package/dist/command/cli/lifecycle/daemon-lifecycle.d.ts +1 -1
- package/dist/command/cli/lifecycle/daemon-lifecycle.js +7 -6
- package/dist/command/cli/mcp-command.js +13 -4
- package/dist/command/cli/ports/port-holder.d.ts +11 -0
- package/dist/command/cli/ports/port-holder.js +36 -4
- package/dist/command/cli/setup-mcp-cli.d.ts +27 -1
- package/dist/command/cli/setup-mcp-cli.js +98 -23
- package/dist/command/cli/status/status-lines.d.ts +2 -0
- package/dist/command/cli/status/status-lines.js +72 -17
- package/dist/command/cli/status-fields.d.ts +5 -1
- package/dist/command/cli/status-fields.js +24 -6
- package/dist/command/cli/try-command.d.ts +7 -0
- package/dist/command/cli/try-command.js +11 -3
- package/dist/command/cli/two-builds/compare-command.d.ts +48 -0
- package/dist/command/cli/two-builds/compare-command.js +314 -0
- package/dist/command/cli/verify-against.d.ts +61 -0
- package/dist/command/cli/verify-against.js +187 -0
- package/dist/command/cli/verify-no-session.d.ts +2 -0
- package/dist/command/cli/verify-no-session.js +13 -0
- package/dist/command/cli/verify-results.d.ts +35 -2
- package/dist/command/cli/verify-results.js +76 -10
- package/dist/command/cli.js +112 -70
- package/dist/command/daemon/binding/daemon-status-probe.d.ts +3 -1
- package/dist/command/daemon/binding/daemon-status-probe.js +4 -2
- package/dist/command/daemon/daemon-argv.d.ts +9 -0
- package/dist/command/daemon/daemon-argv.js +11 -0
- package/dist/command/daemon/daemon-resolve.d.ts +8 -1
- package/dist/command/daemon/daemon-resolve.js +27 -1
- package/dist/command/daemon/daemon.d.ts +22 -4
- package/dist/command/daemon/daemon.js +52 -10
- package/dist/command/daemon/lifetime/daemon-exit.d.ts +24 -0
- package/dist/command/daemon/lifetime/daemon-exit.js +39 -0
- package/dist/command/daemon/lifetime/death-cause.d.ts +15 -0
- package/dist/command/daemon/lifetime/death-cause.js +149 -0
- package/dist/command/daemon/lifetime/heartbeat.d.ts +7 -15
- package/dist/command/daemon/lifetime/heartbeat.js +11 -15
- package/dist/command/daemon/lifetime/startup-failure.d.ts +29 -0
- package/dist/command/daemon/lifetime/startup-failure.js +62 -0
- package/dist/command/daemon/port-state.d.ts +14 -0
- package/dist/command/daemon/port-state.js +31 -0
- package/dist/command/setup/agent-configs.d.ts +2 -0
- package/dist/command/setup/agent-configs.js +42 -15
- package/dist/command/setup/agent-next-steps.d.ts +77 -0
- package/dist/command/setup/agent-next-steps.js +284 -0
- package/dist/command/setup/agent-writer.js +11 -6
- package/dist/command/setup/bringup/dev-server-files.d.ts +9 -0
- package/dist/command/setup/bringup/dev-server-files.js +17 -0
- package/dist/command/setup/bringup/static-page-server.d.ts +14 -2
- package/dist/command/setup/bringup/static-page-server.js +43 -9
- package/dist/command/setup/init/init-host.js +2 -0
- package/dist/command/setup/init/init-runtime.d.ts +6 -1
- package/dist/command/setup/init/init-runtime.js +63 -21
- package/dist/command/setup/mcp-launcher.d.ts +3 -0
- package/dist/command/setup/mcp-launcher.js +11 -0
- package/dist/command/setup/node-effects.d.ts +3 -2
- package/dist/command/setup/node-effects.js +9 -3
- package/dist/command/setup/remaining-steps.d.ts +8 -0
- package/dist/command/setup/remaining-steps.js +10 -0
- package/dist/command/setup/run-setup.d.ts +2 -0
- package/dist/command/setup/run-setup.js +9 -1
- package/dist/command/setup/setup-command.js +36 -15
- package/dist/command/setup/setup-mcp.d.ts +8 -0
- package/dist/command/setup/setup-mcp.js +47 -20
- package/dist/command/setup/start-dev-server.d.ts +8 -0
- package/dist/command/setup/start-dev-server.js +43 -0
- package/dist/command/update/updater.js +6 -0
- package/dist/command/version/identity/server-version.d.ts +2 -0
- package/dist/command/version/identity/server-version.js +2 -0
- package/dist/command/version/version-skew.d.ts +7 -0
- package/dist/command/version/version-skew.js +14 -3
- package/dist/features/compare/action-plan.d.ts +260 -0
- package/dist/features/compare/action-plan.js +543 -0
- package/dist/features/compare/behaviour-compare.d.ts +24 -0
- package/dist/features/compare/behaviour-compare.js +85 -0
- package/dist/features/compare/behaviour-diff.d.ts +97 -0
- package/dist/features/compare/behaviour-diff.js +202 -0
- package/dist/features/compare/behaviour-report.d.ts +40 -0
- package/dist/features/compare/behaviour-report.js +128 -0
- package/dist/features/compare/build-readings.d.ts +106 -0
- package/dist/features/compare/build-readings.js +250 -0
- package/dist/features/compare/compare-diff.d.ts +62 -0
- package/dist/features/compare/compare-diff.js +177 -0
- package/dist/features/compare/compare-report-html.d.ts +6 -0
- package/dist/features/compare/compare-report-html.js +102 -0
- package/dist/features/compare/compare-report.d.ts +15 -0
- package/dist/features/compare/compare-report.js +73 -0
- package/dist/features/compare/compare-run.d.ts +60 -0
- package/dist/features/compare/compare-run.js +123 -0
- package/dist/features/compare/outside/effect-lines.d.ts +26 -0
- package/dist/features/compare/outside/effect-lines.js +103 -0
- package/dist/features/compare/outside/state-sources.d.ts +76 -0
- package/dist/features/compare/outside/state-sources.js +164 -0
- package/dist/features/compare/outside/value-dictionary.d.ts +51 -0
- package/dist/features/compare/outside/value-dictionary.js +227 -0
- package/dist/features/compare/visible-content.d.ts +20 -0
- package/dist/features/compare/visible-content.js +125 -0
- package/dist/features/crawl/crawl-empty.js +2 -1
- package/dist/features/crawl/crawl-exhaustive.d.ts +55 -0
- package/dist/features/crawl/crawl-exhaustive.js +138 -0
- package/dist/features/crawl/crawl-tools.d.ts +9 -2
- package/dist/features/crawl/crawl-tools.js +28 -12
- package/dist/features/crawl/crawl.d.ts +8 -9
- package/dist/features/crawl/crawl.js +61 -65
- package/dist/features/exhaust/app-coverage.d.ts +5 -1
- package/dist/features/exhaust/app-coverage.js +3 -1
- package/dist/features/exhaust/code-coverage.js +8 -4
- package/dist/features/exhaust/edge-cases.d.ts +133 -0
- package/dist/features/exhaust/edge-cases.js +310 -0
- package/dist/features/exhaust/edges/edge-drive.d.ts +42 -0
- package/dist/features/exhaust/edges/edge-drive.js +364 -0
- package/dist/features/exhaust/edges/edge-run.d.ts +59 -0
- package/dist/features/exhaust/edges/edge-run.js +169 -0
- package/dist/features/exhaust/explorer.d.ts +87 -14
- package/dist/features/exhaust/explorer.js +159 -25
- package/dist/features/exhaust/failure-branches.d.ts +5 -0
- package/dist/features/exhaust/failure-branches.js +29 -11
- package/dist/features/exhaust/invariants.d.ts +112 -0
- package/dist/features/exhaust/invariants.js +188 -0
- package/dist/features/exhaust/ledger.d.ts +68 -0
- package/dist/features/exhaust/ledger.js +121 -13
- package/dist/features/exhaust/session-fold.d.ts +10 -0
- package/dist/features/exhaust/session-fold.js +46 -3
- package/dist/features/exhaust/session-port.d.ts +3 -1
- package/dist/features/exhaust/session-port.js +97 -28
- package/dist/features/grade/grade-answer.d.ts +99 -0
- package/dist/features/grade/grade-answer.js +112 -0
- package/dist/features/grade/grade-noise.d.ts +27 -0
- package/dist/features/grade/grade-noise.js +55 -0
- package/dist/features/grade/grade-renumber.d.ts +12 -0
- package/dist/features/grade/grade-renumber.js +86 -0
- package/dist/features/grade/grade-state.d.ts +50 -0
- package/dist/features/grade/grade-state.js +93 -0
- package/dist/features/grade/grade-suggest.d.ts +33 -0
- package/dist/features/grade/grade-suggest.js +98 -0
- package/dist/features/license/license.d.ts +11 -71
- package/dist/features/license/license.js +43 -136
- package/dist/features/visual/desktop-capture.d.ts +33 -0
- package/dist/features/visual/desktop-capture.js +94 -0
- package/dist/features/visual/step-shots.js +139 -35
- package/dist/features/visual/visual-tools.d.ts +0 -18
- package/dist/features/visual/visual-tools.js +67 -99
- package/dist/import-graph.js +5 -6
- package/dist/index.d.ts +3 -1
- package/dist/index.js +19 -7
- package/dist/judgement/capsule/causal-summary.js +9 -2
- package/dist/judgement/runs/artifact/replay-mapping.d.ts +13 -1
- package/dist/judgement/runs/artifact/replay-mapping.js +1 -1
- package/dist/judgement/runs/reticle-runner.js +16 -2
- package/dist/judgement/runs/run-ledger.d.ts +7 -5
- package/dist/judgement/runs/run-ledger.js +98 -69
- package/dist/judgement/runs/runner-port.d.ts +2 -0
- package/dist/judgement/runs/runner-port.js +23 -5
- package/dist/judgement/runs/verification-sync.d.ts +6 -3
- package/dist/judgement/runs/verification-sync.js +21 -10
- package/dist/language/flows/affected-tools.d.ts +1 -1
- package/dist/language/flows/affected-tools.js +49 -30
- package/dist/language/flows/change/git-changed.d.ts +2 -1
- package/dist/language/flows/change/git-changed.js +19 -2
- package/dist/language/flows/fields/flow-secret-field.d.ts +1 -13
- package/dist/language/flows/fields/flow-secret-field.js +77 -21
- package/dist/language/flows/flow-anchor.d.ts +60 -2
- package/dist/language/flows/flow-anchor.js +125 -20
- package/dist/language/flows/flow-classify.d.ts +20 -0
- package/dist/language/flows/flow-classify.js +90 -12
- package/dist/language/flows/flow-cross-step.d.ts +7 -1
- package/dist/language/flows/flow-cross-step.js +25 -1
- package/dist/language/flows/flow-journey.d.ts +38 -4
- package/dist/language/flows/flow-journey.js +138 -8
- package/dist/language/flows/flow-replay-run.d.ts +0 -16
- package/dist/language/flows/flow-replay-run.js +76 -14
- package/dist/language/flows/flow-replay-types.d.ts +4 -2
- package/dist/language/flows/flow-replay.d.ts +19 -1
- package/dist/language/flows/flow-replay.js +169 -52
- package/dist/language/flows/flow-replayable.js +7 -1
- package/dist/language/flows/flow-start-path.d.ts +34 -5
- package/dist/language/flows/flow-start-path.js +202 -15
- package/dist/language/flows/flow-step-runners.d.ts +14 -13
- package/dist/language/flows/flow-step-runners.js +184 -109
- package/dist/language/flows/flow-store-for-session.d.ts +26 -25
- package/dist/language/flows/flow-store-for-session.js +51 -5
- package/dist/language/flows/flow-tools.js +46 -33
- package/dist/language/flows/flows.js +176 -76
- package/dist/language/flows/recording/tape/recordings.d.ts +21 -16
- package/dist/language/flows/recording/tape/recordings.js +112 -49
- package/dist/language/flows/replay.d.ts +36 -12
- package/dist/language/flows/replay.js +193 -50
- package/dist/language/flows/steps/page-steps.d.ts +68 -0
- package/dist/language/flows/steps/page-steps.js +198 -0
- package/dist/language/flows/suite/sequential-run.d.ts +68 -0
- package/dist/language/flows/suite/sequential-run.js +96 -0
- package/dist/language/flows/verify-change-tools.js +26 -15
- package/dist/launch-chromium.d.ts +1 -0
- package/dist/launch-chromium.js +49 -12
- package/dist/memory/cloud/background-sync.d.ts +13 -0
- package/dist/memory/cloud/background-sync.js +24 -3
- package/dist/memory/cloud/sync-daemon.js +26 -27
- package/dist/memory/cloud/sync-screenshots.js +6 -1
- package/dist/memory/journal/attach-journal.js +2 -1
- package/dist/memory/journal/drive-flow.d.ts +7 -0
- package/dist/memory/journal/drive-flow.js +44 -13
- package/dist/memory/journal/journal-recorder.d.ts +4 -2
- package/dist/memory/journal/session-journal.d.ts +7 -0
- package/dist/memory/journal/session-journal.js +58 -10
- package/dist/memory/project/root-claims.d.ts +9 -0
- package/dist/memory/project/root-claims.js +9 -4
- package/dist/portal/bridge/bridge.d.ts +13 -4
- package/dist/portal/bridge/bridge.js +10 -9
- package/dist/portal/input/download-capture.d.ts +7 -0
- package/dist/portal/input/download-capture.js +92 -0
- package/dist/portal/input/network-detail.d.ts +6 -0
- package/dist/portal/input/network-detail.js +12 -1
- package/dist/portal/input/network-mock-tools.js +49 -13
- package/dist/portal/input/network-mock.d.ts +8 -0
- package/dist/portal/input/network-mock.js +30 -10
- package/dist/portal/input/page-capture.d.ts +24 -0
- package/dist/portal/input/page-capture.js +33 -0
- package/dist/portal/input/real-input.d.ts +35 -18
- package/dist/portal/input/real-input.js +99 -33
- package/dist/portal/input/viewport-tools.d.ts +8 -0
- package/dist/portal/input/viewport-tools.js +81 -21
- package/dist/portal/pool/browser-pool.d.ts +16 -30
- package/dist/portal/pool/browser-pool.js +64 -52
- package/dist/portal/pool/context-permissions.d.ts +34 -17
- package/dist/portal/pool/context-permissions.js +94 -1
- package/dist/portal/pool/csp-widen.d.ts +62 -0
- package/dist/portal/pool/csp-widen.js +132 -0
- package/dist/portal/pool/determinism.d.ts +52 -0
- package/dist/portal/pool/determinism.js +259 -0
- package/dist/portal/pool/lease-emulation.d.ts +34 -1
- package/dist/portal/pool/lease-emulation.js +18 -0
- package/dist/portal/pool/lease-marker-stamp.d.ts +6 -0
- package/dist/portal/pool/lease-marker-stamp.js +15 -1
- package/dist/portal/pool/lease-page-error.d.ts +56 -0
- package/dist/portal/pool/lease-page-error.js +160 -0
- package/dist/portal/pool/page-error-journal.d.ts +63 -0
- package/dist/portal/pool/page-error-journal.js +208 -0
- package/dist/portal/pool/playwright-launcher.js +71 -15
- package/dist/portal/pool/pool-contract.d.ts +49 -1
- package/dist/portal/pool/pool-lease.d.ts +70 -2
- package/dist/portal/pool/pool-lease.js +23 -0
- package/dist/portal/pool/zero-install.d.ts +8 -0
- package/dist/portal/pool/zero-install.js +91 -1
- package/dist/portal/session/dev-server/dev-command.d.ts +11 -2
- package/dist/portal/session/dev-server/dev-command.js +110 -16
- package/dist/portal/session/dev-server/dev-server-probe.d.ts +7 -0
- package/dist/portal/session/dev-server/dev-server-probe.js +9 -0
- package/dist/portal/session/dev-server/project-ports.d.ts +21 -0
- package/dist/portal/session/dev-server/project-ports.js +62 -0
- package/dist/portal/session/facts/departed-network.d.ts +66 -0
- package/dist/portal/session/facts/departed-network.js +142 -0
- package/dist/portal/session/no-session-diagnosis.d.ts +9 -0
- package/dist/portal/session/no-session-diagnosis.js +76 -27
- package/dist/portal/session/no-session-next-action.d.ts +14 -0
- package/dist/portal/session/no-session-next-action.js +56 -7
- package/dist/portal/session/no-session-watch.d.ts +6 -0
- package/dist/portal/session/no-session-watch.js +41 -4
- package/dist/portal/session/page-commands/undeclared-command.d.ts +9 -3
- package/dist/portal/session/page-commands/undeclared-command.js +16 -7
- package/dist/portal/session/recorded-gaps.d.ts +1 -1
- package/dist/portal/session/recorded-gaps.js +5 -2
- package/dist/portal/session/serving-directory.d.ts +10 -0
- package/dist/portal/session/serving-directory.js +39 -8
- package/dist/portal/session/session-manager.d.ts +10 -0
- package/dist/portal/session/session-manager.js +211 -81
- package/dist/portal/session/session.js +4 -3
- package/dist/portal/session/zero-install-first-call.d.ts +20 -1
- package/dist/portal/session/zero-install-first-call.js +34 -2
- package/dist/start-wiring.d.ts +30 -2
- package/dist/start-wiring.js +74 -9
- package/dist/surface/mcp/mcp-proxy.js +10 -2
- package/dist/surface/mcp/mcp.js +8 -5
- package/dist/surface/mcp/peer/agents-connected.d.ts +6 -0
- package/dist/surface/mcp/peer/agents-connected.js +58 -0
- package/dist/surface/mcp/proxy/proxy-daemon-probe.js +17 -5
- package/dist/surface/mcp/shared-params.d.ts +2 -0
- package/dist/surface/mcp/shared-params.js +31 -0
- package/dist/surface/tools/act/act-observation.d.ts +40 -2
- package/dist/surface/tools/act/act-observation.js +118 -12
- package/dist/surface/tools/act/act-preflight.js +38 -18
- package/dist/surface/tools/act/act-sequence-retry.d.ts +23 -2
- package/dist/surface/tools/act/act-sequence-retry.js +61 -5
- package/dist/surface/tools/act/act-target.js +3 -1
- package/dist/surface/tools/act/after-match.d.ts +10 -0
- package/dist/surface/tools/act/after-match.js +40 -0
- package/dist/surface/tools/act/already-true.js +1 -1
- package/dist/surface/tools/act/navigation/reload-result.d.ts +12 -1
- package/dist/surface/tools/act/navigation/reload-result.js +25 -2
- package/dist/surface/tools/act/resolve-target.d.ts +17 -1
- package/dist/surface/tools/act/resolve-target.js +59 -9
- package/dist/surface/tools/act/sequence-grade.d.ts +15 -1
- package/dist/surface/tools/act/sequence-grade.js +45 -27
- package/dist/surface/tools/act/settle-in-flight.js +27 -15
- package/dist/surface/tools/act-command.d.ts +23 -0
- package/dist/surface/tools/act-command.js +50 -0
- package/dist/surface/tools/act-sequence-tool.js +81 -50
- package/dist/surface/tools/act-tools.d.ts +1 -13
- package/dist/surface/tools/act-tools.js +83 -95
- package/dist/surface/tools/agent/build-staleness.d.ts +1 -1
- package/dist/surface/tools/agent/build-staleness.js +1 -1
- package/dist/surface/tools/assert/assert-verdict.js +18 -12
- package/dist/surface/tools/browser-tools.js +87 -13
- package/dist/surface/tools/compare-tools.d.ts +11 -0
- package/dist/surface/tools/compare-tools.js +94 -0
- package/dist/surface/tools/coverage-tools.js +1 -1
- package/dist/surface/tools/dynamic-tools.js +11 -3
- package/dist/surface/tools/empty-session-list.d.ts +2 -0
- package/dist/surface/tools/empty-session-list.js +4 -2
- package/dist/surface/tools/error-recovery.d.ts +12 -1
- package/dist/surface/tools/error-recovery.js +131 -40
- package/dist/surface/tools/explore-tools.js +1 -1
- package/dist/surface/tools/invoke-tool.js +29 -21
- package/dist/surface/tools/lease-coverage-tools.d.ts +49 -0
- package/dist/surface/tools/lease-coverage-tools.js +128 -0
- package/dist/surface/tools/lease-hint.d.ts +42 -1
- package/dist/surface/tools/lease-hint.js +52 -3
- package/dist/surface/tools/lease-readiness.d.ts +47 -0
- package/dist/surface/tools/lease-readiness.js +126 -0
- package/dist/surface/tools/lease-seed.d.ts +16 -1
- package/dist/surface/tools/lease-seed.js +37 -4
- package/dist/surface/tools/lease-tools.d.ts +3 -41
- package/dist/surface/tools/lease-tools.js +104 -124
- package/dist/surface/tools/lease-url-identity.d.ts +46 -0
- package/dist/surface/tools/lease-url-identity.js +97 -0
- package/dist/surface/tools/merge-tools.js +99 -2
- package/dist/surface/tools/merged-name-redirect.js +5 -1
- package/dist/surface/tools/observe-tools.js +15 -9
- package/dist/surface/tools/read/console-pre-attach.d.ts +19 -0
- package/dist/surface/tools/read/console-pre-attach.js +23 -0
- package/dist/surface/tools/read-tools.js +33 -30
- package/dist/surface/tools/real-input-attempt.d.ts +0 -8
- package/dist/surface/tools/real-input-attempt.js +143 -28
- package/dist/surface/tools/repeat-hint.d.ts +40 -0
- package/dist/surface/tools/repeat-hint.js +171 -0
- package/dist/surface/tools/run-report.d.ts +2 -2
- package/dist/surface/tools/run-report.js +27 -15
- package/dist/surface/tools/session-ports.d.ts +14 -0
- package/dist/surface/tools/session-ports.js +53 -0
- package/dist/surface/tools/tool-kit.d.ts +18 -0
- package/dist/surface/tools/tool-kit.js +9 -3
- package/dist/surface/tools/tool-surface.js +0 -1
- package/dist/surface/tools/tools.js +54 -18
- package/dist/surface/tools/type-spelling.d.ts +13 -0
- package/dist/surface/tools/type-spelling.js +36 -0
- package/dist/telemetry/agent-client.d.ts +6 -0
- package/dist/telemetry/agent-client.js +10 -0
- package/dist/telemetry/argument-shape.js +17 -7
- package/dist/telemetry/cli-telemetry.d.ts +10 -1
- package/dist/telemetry/cli-telemetry.js +15 -0
- package/dist/telemetry/init-telemetry.d.ts +2 -1
- package/dist/telemetry/init-telemetry.js +47 -2
- package/dist/telemetry/install-trace.js +23 -4
- package/dist/telemetry/onboarding-funnel.d.ts +19 -2
- package/dist/telemetry/onboarding-funnel.js +25 -3
- package/dist/telemetry/proxy-telemetry.d.ts +2 -1
- package/dist/telemetry/proxy-telemetry.js +2 -2
- package/dist/telemetry/session-metrics.d.ts +1 -1
- package/dist/telemetry/session-metrics.js +67 -27
- package/dist/telemetry/telemetry.d.ts +3 -1
- package/dist/telemetry/telemetry.js +60 -28
- package/dist/telemetry/tool-refused.d.ts +6 -3
- package/dist/telemetry/tool-refused.js +15 -3
- package/docs/actions.mdx +3 -3
- package/docs/agent-cheatsheet.md +1 -1
- package/docs/architecture.md +3 -6
- package/docs/capabilities.mdx +61 -1
- package/docs/cli/affected.mdx +9 -7
- package/docs/cli/cloud.mdx +3 -3
- package/docs/cli/compare.mdx +94 -0
- package/docs/cli/doctor.mdx +41 -2
- package/docs/cli/grade.mdx +97 -0
- package/docs/cli/help.mdx +4 -2
- package/docs/cli/license.mdx +13 -12
- package/docs/cli/restart.mdx +7 -5
- package/docs/cli/rollback.mdx +5 -5
- package/docs/cli/setup.mdx +13 -2
- package/docs/cli/status.mdx +4 -0
- package/docs/cli/stop.mdx +3 -3
- package/docs/cli/update.mdx +2 -2
- package/docs/cli/verify.mdx +23 -1
- package/docs/cli.mdx +34 -24
- package/docs/deploy-checks.md +25 -0
- package/docs/desktop.mdx +1 -1
- package/docs/enterprise.md +17 -22
- package/docs/environment.mdx +1 -0
- package/docs/first-drive.md +1 -1
- package/docs/flows.md +11 -1
- package/docs/frameworks.mdx +9 -9
- package/docs/getting-started.md +1 -1
- package/docs/install-manual.mdx +16 -1
- package/docs/license-activation.mdx +3 -3
- package/docs/packages/engine.mdx +1 -1
- package/docs/packages/server.mdx +1 -1
- package/docs/packages/tauri.mdx +1 -1
- package/docs/packages/test.mdx +1 -1
- package/docs/packages.mdx +1 -1
- package/docs/platform-integration.md +1 -1
- package/docs/predicates.mdx +1 -1
- package/docs/quickstart.mdx +68 -49
- package/docs/skill-setup.md +1 -1
- package/docs/skill.md +14 -13
- package/docs/telemetry.md +6 -4
- package/docs/tools/navigate.mdx +1 -1
- package/docs/tools/verify.mdx +4 -1
- package/docs/troubleshooting.mdx +1 -1
- package/docs/usage.md +17 -14
- package/package.json +18 -14
- package/LICENSE-ENTERPRISE +0 -37
- package/dist/features/ee/audit-log.d.ts +0 -14
- package/dist/features/ee/audit-log.js +0 -11
- package/dist/features/exhaust/exhaust-run.d.ts +0 -33
- package/dist/features/exhaust/exhaust-run.js +0 -82
|
@@ -5,7 +5,7 @@ import { ReticleTool } from '@reticlehq/core';
|
|
|
5
5
|
import { asNumber, asString } from '@reticlehq/core';
|
|
6
6
|
import { stepCountSchema, timeoutMsSchema } from '../../surface/tools/args/numeric-bounds.js';
|
|
7
7
|
import { crawl } from './crawl.js';
|
|
8
|
-
import {
|
|
8
|
+
import { runExhaustive } from './crawl-exhaustive.js';
|
|
9
9
|
import { heuristicFillValue } from '../harness/fill-values.js';
|
|
10
10
|
import { secretEnvKey } from '../../language/flows/flows.js';
|
|
11
11
|
import { CRAWL_DEFAULTS } from '@reticlehq/core';
|
|
@@ -16,6 +16,24 @@ const nodeSleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
|
16
16
|
* from whatever page is on screen, so without the check a page on another origin could name an
|
|
17
17
|
* empty textbox after a secret and have the crawl type it in.
|
|
18
18
|
*/
|
|
19
|
+
/**
|
|
20
|
+
* How the exhaustive crawl breaks a request: the driven browser first, then the leased page the
|
|
21
|
+
* session runs in. `reticle mcp --headless` drives a lease, and the mock tool already reaches it the
|
|
22
|
+
* same way. Undefined when neither exists, so the crawl says why its failure paths were skipped.
|
|
23
|
+
*/
|
|
24
|
+
export function exhaustiveMocks(deps, sessionId) {
|
|
25
|
+
const driven = deps.realInput?.setMocks?.bind(deps.realInput);
|
|
26
|
+
const pool = deps.pool;
|
|
27
|
+
if (driven === undefined && pool === undefined) {
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
return async (url, rules) => {
|
|
31
|
+
if (driven !== undefined && (await driven(url, rules))) {
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
return true === (await pool?.setMocksLease(sessionId, rules));
|
|
35
|
+
};
|
|
36
|
+
}
|
|
19
37
|
export function crawlSecret(startUrl, env) {
|
|
20
38
|
const originOf = (url) => {
|
|
21
39
|
try {
|
|
@@ -29,14 +47,14 @@ export function crawlSecret(startUrl, env) {
|
|
|
29
47
|
return (name, pageUrl) => home !== undefined && home === originOf(pageUrl) ? env[secretEnvKey(name)] : undefined;
|
|
30
48
|
}
|
|
31
49
|
/**
|
|
32
|
-
* The autonomous "smart monkey" tool. Builds on
|
|
50
|
+
* The autonomous "smart monkey" tool. Builds on the interactive snapshot (which only LISTS) by
|
|
33
51
|
* actually clicking each reachable control and classifying the reaction. DESTRUCTIVE by nature —
|
|
34
52
|
* it drives the app — so it's an explicit, bounded tool, never part of a passive read.
|
|
35
53
|
*/
|
|
36
54
|
export const CRAWL_TOOLS = [
|
|
37
55
|
{
|
|
38
56
|
name: ReticleTool.CRAWL,
|
|
39
|
-
description: 'Autonomously click every reachable interactive control (bounded by maxSteps, default 25) and report anomalies WITHOUT a script. Two classes: single-channel faults (console errors, failed requests ≥400, DEAD controls that dispatched but did nothing) and CONTRADICTIONS — two channels disagreeing about the same click, e.g. the UI advanced while its write failed, a success signal fired over a failed request, a write succeeded and nothing changed, the same write fired twice, or the UI moved on over an in-flight request. Contradictions are the false greens a human cannot see, because a human watches one channel (the screen) and it looks correct. DESTRUCTIVE — it really clicks (may navigate/mutate state); use
|
|
57
|
+
description: 'Autonomously click every reachable interactive control (bounded by maxSteps, default 25) and report anomalies WITHOUT a script. Two classes: single-channel faults (console errors, failed requests ≥400, DEAD controls that dispatched but did nothing) and CONTRADICTIONS — two channels disagreeing about the same click, e.g. the UI advanced while its write failed, a success signal fired over a failed request, a write succeeded and nothing changed, the same write fired twice, or the UI moved on over an in-flight request. Contradictions are the false greens a human cannot see, because a human watches one channel (the screen) and it looks correct. DESTRUCTIVE — it really clicks (may navigate/mutate state); use reticle_snapshot { mode: "interactive" } first for a non-destructive list. Returns { interactiveFound, stepsRun, anomalies[{kind,ref,desc,detail}], counts, visited, truncated }.',
|
|
40
58
|
inputSchema: {
|
|
41
59
|
maxSteps: stepCountSchema
|
|
42
60
|
.optional()
|
|
@@ -51,7 +69,7 @@ export const CRAWL_TOOLS = [
|
|
|
51
69
|
exhaustive: z
|
|
52
70
|
.boolean()
|
|
53
71
|
.optional()
|
|
54
|
-
.describe("Walk every state it can tell apart, drive each write's failure path, fold into the coverage ledger."),
|
|
72
|
+
.describe("Walk every state it can tell apart, drive each write's failure path and edge cases, fold into the coverage ledger."),
|
|
55
73
|
confirmDangerous: z
|
|
56
74
|
.boolean()
|
|
57
75
|
.optional()
|
|
@@ -91,7 +109,7 @@ export const CRAWL_TOOLS = [
|
|
|
91
109
|
.array(z.object({ ref: z.string(), desc: z.string(), reason: z.string() }))
|
|
92
110
|
.optional()
|
|
93
111
|
.describe('Controls clicked whose silence could not be judged, e.g. on a background tab. They are NOT reported as dead, and they are not known to work either.'),
|
|
94
|
-
// Only on `exhaustive` — see
|
|
112
|
+
// Only on `exhaustive` — see crawl-exhaustive.ts. Loose: it carries the levels.
|
|
95
113
|
exhaustive: z.record(z.string(), z.unknown()).optional(),
|
|
96
114
|
},
|
|
97
115
|
handler: async (deps, args) => {
|
|
@@ -111,11 +129,12 @@ export const CRAWL_TOOLS = [
|
|
|
111
129
|
// Starts code-coverage collection, so the drive below is measured. See takeCodeCoverage.
|
|
112
130
|
await deps.realInput?.takeCodeCoverage?.(session.url);
|
|
113
131
|
const secret = crawlSecret(session.url, process.env);
|
|
132
|
+
const mocks = exhaustiveMocks(deps, session.id);
|
|
114
133
|
return runExhaustive({
|
|
115
134
|
sessions: deps.sessions,
|
|
116
135
|
session,
|
|
117
136
|
startUrl: session.url,
|
|
118
|
-
maxActions: maxSteps
|
|
137
|
+
...(maxSteps === undefined ? {} : { maxActions: maxSteps }),
|
|
119
138
|
settleMs: settleMs ?? CRAWL_DEFAULTS.SETTLE_MS,
|
|
120
139
|
fillValue: (label, pageUrl) => {
|
|
121
140
|
const name = /"([^"]*)"/.exec(label)?.[1] ?? label;
|
|
@@ -123,11 +142,7 @@ export const CRAWL_TOOLS = [
|
|
|
123
142
|
},
|
|
124
143
|
now: deps.now,
|
|
125
144
|
sleep: nodeSleep,
|
|
126
|
-
...(
|
|
127
|
-
? {}
|
|
128
|
-
: {
|
|
129
|
-
setMocks: (url, rules) => deps.realInput?.setMocks?.(url, rules) ?? Promise.resolve(false),
|
|
130
|
-
}),
|
|
145
|
+
...(mocks === undefined ? {} : { setMocks: mocks }),
|
|
131
146
|
...(deps.realInput?.takeCodeCoverage === undefined
|
|
132
147
|
? {}
|
|
133
148
|
: {
|
|
@@ -142,8 +157,9 @@ export const CRAWL_TOOLS = [
|
|
|
142
157
|
...(initialRoute === undefined ? [] : [initialRoute]),
|
|
143
158
|
...routesFromEvents(session.eventsSince(since)),
|
|
144
159
|
];
|
|
145
|
-
if (routes.length > 0)
|
|
160
|
+
if (routes.length > 0) {
|
|
146
161
|
await projectForRoot(deps, sessionRoot(deps, session.id)).recordRoutes(routes);
|
|
162
|
+
}
|
|
147
163
|
return report;
|
|
148
164
|
},
|
|
149
165
|
},
|
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The autonomous "smart monkey": discover every reachable interactive control once, click each
|
|
3
|
+
* (bounded by `maxSteps`) and classify the reaction into anomalies — console errors, failed
|
|
4
|
+
* requests, and DEAD controls (dispatched but the app did nothing). Pure orchestration over a
|
|
5
|
+
* `CrawlSession`, so no browser or Node imports. Single-pass by design, so it always terminates.
|
|
6
|
+
*/
|
|
7
|
+
import { legitimatelyInert } from '../exhaust/session-fold.js';
|
|
1
8
|
import { type CommandResult, type ReticleEvent } from '@reticlehq/core';
|
|
2
9
|
/** The slice of Session the crawler needs — so tests inject a fake without a live browser. */
|
|
3
10
|
export interface CrawlSession {
|
|
@@ -94,13 +101,5 @@ export interface CrawlOptions {
|
|
|
94
101
|
scope?: string;
|
|
95
102
|
confirmDangerous?: boolean;
|
|
96
103
|
}
|
|
97
|
-
/**
|
|
98
|
-
* Controls whose correct behaviour IS to do nothing when clicked.
|
|
99
|
-
*
|
|
100
|
-
* Read off the same descriptor the crawler already has, so no extra round trip: `[disabled]` is inert
|
|
101
|
-
* by definition, an already `[checked]` radio/checkbox is idempotent, and clicking a text field
|
|
102
|
-
* focuses it rather than changing anything.
|
|
103
|
-
*/
|
|
104
|
-
export declare function legitimatelyInert(desc: string): boolean;
|
|
105
104
|
export declare function crawl(session: CrawlSession, opts: CrawlOptions, sleep: CrawlSleep): Promise<CrawlReport>;
|
|
106
|
-
export {};
|
|
105
|
+
export { legitimatelyInert };
|
|
@@ -4,8 +4,9 @@
|
|
|
4
4
|
* requests, and DEAD controls (dispatched but the app did nothing). Pure orchestration over a
|
|
5
5
|
* `CrawlSession`, so no browser or Node imports. Single-pass by design, so it always terminates.
|
|
6
6
|
*/
|
|
7
|
+
import { isActivity, legitimatelyInert } from '../exhaust/session-fold.js';
|
|
7
8
|
import { span } from '../../trace.js';
|
|
8
|
-
import { ActionType, CRAWL_DEFAULTS, CrawlAnomalyKind, DANGEROUS_ACTION_CONFIRM_ARG,
|
|
9
|
+
import { ActionType, CRAWL_DEFAULTS, CrawlAnomalyKind, DANGEROUS_ACTION_CONFIRM_ARG, ReticleCommand, } from '@reticlehq/core';
|
|
9
10
|
import { crawlEmptyNote } from './crawl-empty.js';
|
|
10
11
|
import { failedRequests, isConsoleError } from '../exhaust/session-fold.js';
|
|
11
12
|
import { asNumber, asRecord, asString } from '@reticlehq/core';
|
|
@@ -40,20 +41,12 @@ function revealedControlsNote(count) {
|
|
|
40
41
|
*/
|
|
41
42
|
const ENDS_SESSION = /\b(?:sign ?out|log ?out|sign ?off|log ?off)\b/i;
|
|
42
43
|
const ENDS_SESSION_NOT_PRESSED = 'not pressed: it ends the signed-in session the crawl is driving';
|
|
44
|
+
const NO_ACCESSIBLE_NAME_NOT_PRESSED = 'not pressed: no accessible name, so the destructive-action guard cannot classify it (set confirmDangerous to include it)';
|
|
45
|
+
// A snapshot line carries the accessible name, a value or an own text as a quoted string. A line
|
|
46
|
+
// with no quote has no text at all, for example an icon-only button.
|
|
47
|
+
const HAS_TEXT = /"/;
|
|
43
48
|
/** Why a silent control on a throttled page is not reported as dead. */
|
|
44
49
|
const BACKGROUND_TAB_NOT_JUDGED = 'not judged: the page is throttled (its tab is in the background, or it has not been heard from recently), so a reaction may not show inside the sample window; bring the tab to the front and crawl again';
|
|
45
|
-
/** Any buffer event that proves the app reacted to a click (vs a dead/no-op control). */
|
|
46
|
-
function isActivity(e) {
|
|
47
|
-
return (e.type === EventType.NET_REQUEST ||
|
|
48
|
-
e.type === EventType.DOM_ADDED ||
|
|
49
|
-
e.type === EventType.DOM_REMOVED ||
|
|
50
|
-
e.type === EventType.DOM_ATTR ||
|
|
51
|
-
e.type === EventType.DOM_TEXT ||
|
|
52
|
-
e.type === EventType.ROUTE_CHANGE ||
|
|
53
|
-
e.type === EventType.SIGNAL ||
|
|
54
|
-
e.type === EventType.ANIM_START ||
|
|
55
|
-
e.type === EventType.ANIM_END);
|
|
56
|
-
}
|
|
57
50
|
const SOURCE_FRAME = /^(.*):(\d+):(\d+)$/;
|
|
58
51
|
const COMPONENT_STACK_FIELDS = ['componentStack', 'message'];
|
|
59
52
|
const URL_SOURCE_PROTOCOLS = new Set([
|
|
@@ -66,44 +59,54 @@ const URL_SOURCE_PROTOCOLS = new Set([
|
|
|
66
59
|
/** Turn a browser/dev-server frame URL into the file-shaped pointer the rest of Reticle emits. */
|
|
67
60
|
function sourceFile(raw) {
|
|
68
61
|
let file = raw.trim();
|
|
69
|
-
if (0 === file.length)
|
|
62
|
+
if (0 === file.length) {
|
|
70
63
|
return undefined;
|
|
64
|
+
}
|
|
71
65
|
try {
|
|
72
66
|
const parsed = new URL(file);
|
|
73
|
-
if (URL_SOURCE_PROTOCOLS.has(parsed.protocol))
|
|
67
|
+
if (URL_SOURCE_PROTOCOLS.has(parsed.protocol)) {
|
|
74
68
|
file = decodeURIComponent(parsed.pathname);
|
|
69
|
+
}
|
|
75
70
|
}
|
|
76
71
|
catch {
|
|
77
72
|
// A repo-relative or Windows path is already the more useful form; keep it as-is.
|
|
78
73
|
}
|
|
79
74
|
file = file.replace(/[?#].*$/, '').replace(/\\/g, '/');
|
|
80
75
|
const bundledPrefix = file.lastIndexOf('/./');
|
|
81
|
-
if (-1 !== bundledPrefix)
|
|
76
|
+
if (-1 !== bundledPrefix) {
|
|
82
77
|
file = file.slice(bundledPrefix + 3);
|
|
83
|
-
|
|
78
|
+
}
|
|
79
|
+
if (file.startsWith('/@fs/')) {
|
|
84
80
|
file = file.slice('/@fs/'.length);
|
|
85
|
-
|
|
81
|
+
}
|
|
82
|
+
if (/^\/[A-Za-z]:\//.test(file)) {
|
|
86
83
|
file = file.slice(1);
|
|
87
|
-
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
88
86
|
file = file.replace(/^\/+/, '');
|
|
89
|
-
|
|
87
|
+
}
|
|
88
|
+
if (file.startsWith('./')) {
|
|
90
89
|
file = file.slice(2);
|
|
90
|
+
}
|
|
91
91
|
return 0 === file.length ? undefined : file;
|
|
92
92
|
}
|
|
93
93
|
/** The location part of a Chrome/React or Firefox stack frame. */
|
|
94
94
|
function frameLocation(line) {
|
|
95
95
|
let frame = line.trim();
|
|
96
|
-
if (frame.startsWith('at '))
|
|
96
|
+
if (frame.startsWith('at ')) {
|
|
97
97
|
frame = frame.slice(3).trim();
|
|
98
|
+
}
|
|
98
99
|
else {
|
|
99
100
|
const firefoxSeparator = frame.lastIndexOf('@');
|
|
100
|
-
if (-1 === firefoxSeparator)
|
|
101
|
+
if (-1 === firefoxSeparator) {
|
|
101
102
|
return undefined;
|
|
103
|
+
}
|
|
102
104
|
frame = frame.slice(firefoxSeparator + 1);
|
|
103
105
|
}
|
|
104
106
|
const opening = frame.lastIndexOf('(');
|
|
105
|
-
if (-1 !== opening && frame.endsWith(')'))
|
|
107
|
+
if (-1 !== opening && frame.endsWith(')')) {
|
|
106
108
|
return frame.slice(opening + 1, -1);
|
|
109
|
+
}
|
|
107
110
|
return frame;
|
|
108
111
|
}
|
|
109
112
|
function isFrameworkSource(file) {
|
|
@@ -114,14 +117,17 @@ function isFrameworkSource(file) {
|
|
|
114
117
|
function sourceFromStack(stack) {
|
|
115
118
|
for (const line of stack.split('\n')) {
|
|
116
119
|
const location = frameLocation(line);
|
|
117
|
-
if (location === undefined)
|
|
120
|
+
if (location === undefined) {
|
|
118
121
|
continue;
|
|
122
|
+
}
|
|
119
123
|
const match = SOURCE_FRAME.exec(location);
|
|
120
|
-
if (null === match)
|
|
124
|
+
if (null === match) {
|
|
121
125
|
continue;
|
|
126
|
+
}
|
|
122
127
|
const [, rawFile, rawLine] = match;
|
|
123
|
-
if (rawFile === undefined || rawLine === undefined)
|
|
128
|
+
if (rawFile === undefined || rawLine === undefined) {
|
|
124
129
|
continue;
|
|
130
|
+
}
|
|
125
131
|
const file = sourceFile(rawFile);
|
|
126
132
|
const lineNumber = Number(rawLine);
|
|
127
133
|
if (file === undefined ||
|
|
@@ -140,55 +146,33 @@ function sourceFromConsoleError(event) {
|
|
|
140
146
|
// observer carries it inside `message`; richer producers can send `componentStack` directly.
|
|
141
147
|
for (const field of COMPONENT_STACK_FIELDS) {
|
|
142
148
|
const componentStack = asString(event.data[field]);
|
|
143
|
-
if (componentStack === undefined)
|
|
149
|
+
if (componentStack === undefined) {
|
|
144
150
|
continue;
|
|
151
|
+
}
|
|
145
152
|
const componentSource = sourceFromStack(componentStack);
|
|
146
|
-
if (componentSource !== undefined)
|
|
153
|
+
if (componentSource !== undefined) {
|
|
147
154
|
return componentSource;
|
|
155
|
+
}
|
|
148
156
|
}
|
|
149
157
|
// The ordinary error stack can also identify a component better than ErrorEvent.filename (for
|
|
150
158
|
// example, a bundle or click-handler frame), so try app frames before the structured fallback.
|
|
151
159
|
const stack = asString(event.data['stack']);
|
|
152
160
|
if (stack !== undefined) {
|
|
153
161
|
const stackSource = sourceFromStack(stack);
|
|
154
|
-
if (stackSource !== undefined)
|
|
162
|
+
if (stackSource !== undefined) {
|
|
155
163
|
return stackSource;
|
|
164
|
+
}
|
|
156
165
|
}
|
|
157
166
|
const directFile = asString(event.data['source']);
|
|
158
167
|
const directLine = asNumber(event.data['line']);
|
|
159
|
-
if (directFile === undefined || directLine === undefined || directLine <= 0)
|
|
168
|
+
if (directFile === undefined || directLine === undefined || directLine <= 0) {
|
|
160
169
|
return undefined;
|
|
170
|
+
}
|
|
161
171
|
const file = sourceFile(directFile);
|
|
162
172
|
return file === undefined || isFrameworkSource(file)
|
|
163
173
|
? undefined
|
|
164
174
|
: `${file}:${String(directLine)}`;
|
|
165
175
|
}
|
|
166
|
-
/**
|
|
167
|
-
* Controls whose correct behaviour IS to do nothing when clicked.
|
|
168
|
-
*
|
|
169
|
-
* Read off the same descriptor the crawler already has, so no extra round trip: `[disabled]` is inert
|
|
170
|
-
* by definition, an already `[checked]` radio/checkbox is idempotent, and clicking a text field
|
|
171
|
-
* focuses it rather than changing anything.
|
|
172
|
-
*/
|
|
173
|
-
export function legitimatelyInert(desc) {
|
|
174
|
-
if (desc.includes('[disabled]'))
|
|
175
|
-
return true;
|
|
176
|
-
if (desc.includes('[checked]'))
|
|
177
|
-
return true;
|
|
178
|
-
// Inputs, and the ARIA LIVE-REGION roles.
|
|
179
|
-
//
|
|
180
|
-
// `alert`, `status`, `log`, `timer` and `marquee` exist to be READ — a screen reader announces
|
|
181
|
-
// them and nothing is meant to happen when one is clicked. Clicking twice and seeing nothing is
|
|
182
|
-
// therefore correct behaviour, and reporting it as `dead-control` accuses a working app.
|
|
183
|
-
//
|
|
184
|
-
// Found by driving a real app: the demo reported `dead-control — alert "Invalid email or
|
|
185
|
-
// password"`, which is a login form correctly telling somebody their password was wrong. That is
|
|
186
|
-
// the most expensive kind of false positive there is — the strongest claim the crawler makes,
|
|
187
|
-
// about an app that was working, and on the path a new user meets first.
|
|
188
|
-
//
|
|
189
|
-
// `alertdialog` is deliberately NOT here: it is interactive, and a word boundary keeps it out.
|
|
190
|
-
return /\b(textbox|searchbox|combobox|spinbutton|alert|status|log|timer|marquee)\b/.test(desc);
|
|
191
|
-
}
|
|
192
176
|
/**
|
|
193
177
|
* How many interactive controls exist now that did not exist when the crawl enumerated.
|
|
194
178
|
*
|
|
@@ -202,8 +186,9 @@ async function revealedItems(session, opts, before) {
|
|
|
202
186
|
mode: 'interactive',
|
|
203
187
|
...(opts.scope !== undefined ? { scope: opts.scope } : {}),
|
|
204
188
|
});
|
|
205
|
-
if (!snap.ok)
|
|
189
|
+
if (!snap.ok) {
|
|
206
190
|
return [];
|
|
191
|
+
}
|
|
207
192
|
const tree = (snap.result ?? {}).tree ?? '';
|
|
208
193
|
const known = new Set(before.map((i) => (i.ref !== '' ? i.ref : i.desc)));
|
|
209
194
|
return parseInteractive(tree).filter((i) => !known.has(i.ref !== '' ? i.ref : i.desc));
|
|
@@ -237,8 +222,9 @@ async function stillSilent(session, item, settleMs, confirm, sleep) {
|
|
|
237
222
|
});
|
|
238
223
|
await sleep(settleMs);
|
|
239
224
|
// A control that has GONE (the first click removed it) is not a dead control — it did something.
|
|
240
|
-
if (!again.ok || false === asRecord(again.result)['dispatched'])
|
|
225
|
+
if (!again.ok || false === asRecord(again.result)['dispatched']) {
|
|
241
226
|
return false;
|
|
227
|
+
}
|
|
242
228
|
return !session.eventsSince(since).some(isActivity);
|
|
243
229
|
}
|
|
244
230
|
catch {
|
|
@@ -277,18 +263,24 @@ export async function crawl(session, opts, sleep) {
|
|
|
277
263
|
// so one call finished a deletion nobody authorized the second half of.
|
|
278
264
|
const confirm = 0 === round && true === opts.confirmDangerous;
|
|
279
265
|
for (const item of frontier) {
|
|
280
|
-
if (stepsRun >= maxSteps)
|
|
266
|
+
if (stepsRun >= maxSteps) {
|
|
281
267
|
break;
|
|
268
|
+
}
|
|
282
269
|
// Dedupe by ref (the unique element), not by label — two "Delete"/"Edit" controls share a desc
|
|
283
270
|
// but are different controls; collapsing them by label under-covers list/table UIs.
|
|
284
271
|
const dedupeKey = item.ref !== '' ? item.ref : item.desc;
|
|
285
|
-
if (seen.has(dedupeKey))
|
|
286
|
-
continue;
|
|
272
|
+
if (seen.has(dedupeKey)) {
|
|
273
|
+
continue;
|
|
274
|
+
} // don't re-click the same control
|
|
287
275
|
seen.add(dedupeKey);
|
|
288
276
|
if (ENDS_SESSION.test(item.desc)) {
|
|
289
277
|
notJudged.push({ ref: item.ref, desc: item.desc, reason: ENDS_SESSION_NOT_PRESSED });
|
|
290
278
|
continue;
|
|
291
279
|
}
|
|
280
|
+
if (!confirm && !HAS_TEXT.test(item.desc)) {
|
|
281
|
+
notJudged.push({ ref: item.ref, desc: item.desc, reason: NO_ACCESSIBLE_NAME_NOT_PRESSED });
|
|
282
|
+
continue;
|
|
283
|
+
}
|
|
292
284
|
stepsRun += 1;
|
|
293
285
|
visited.push(item.desc);
|
|
294
286
|
const since = session.elapsed();
|
|
@@ -321,8 +313,9 @@ export async function crawl(session, opts, sleep) {
|
|
|
321
313
|
});
|
|
322
314
|
}
|
|
323
315
|
catch (err) {
|
|
324
|
-
if (!isSessionReplacedError(err))
|
|
316
|
+
if (!isSessionReplacedError(err)) {
|
|
325
317
|
throw err;
|
|
318
|
+
}
|
|
326
319
|
navigatedAway = true;
|
|
327
320
|
}
|
|
328
321
|
finally {
|
|
@@ -433,11 +426,13 @@ export async function crawl(session, opts, sleep) {
|
|
|
433
426
|
}
|
|
434
427
|
}
|
|
435
428
|
}
|
|
436
|
-
if (coverageCapped || stepsRun >= maxSteps || REVEAL_ROUNDS <= round)
|
|
429
|
+
if (coverageCapped || stepsRun >= maxSteps || REVEAL_ROUNDS <= round) {
|
|
437
430
|
break;
|
|
431
|
+
}
|
|
438
432
|
const next = await revealedItems(session, opts, found);
|
|
439
|
-
if (0 === next.length)
|
|
433
|
+
if (0 === next.length) {
|
|
440
434
|
break;
|
|
435
|
+
}
|
|
441
436
|
found.push(...next);
|
|
442
437
|
frontier = next;
|
|
443
438
|
}
|
|
@@ -450,7 +445,7 @@ export async function crawl(session, opts, sleep) {
|
|
|
450
445
|
// one would only muddy it.
|
|
451
446
|
const revealed = coverageCapped ? 0 : (await revealedItems(session, opts, found)).length;
|
|
452
447
|
// Left alone on purpose, so not a step the budget ran out before.
|
|
453
|
-
const leftAlone = notJudged.filter((n) => ENDS_SESSION_NOT_PRESSED === n.reason).length;
|
|
448
|
+
const leftAlone = notJudged.filter((n) => ENDS_SESSION_NOT_PRESSED === n.reason || NO_ACCESSIBLE_NAME_NOT_PRESSED === n.reason).length;
|
|
454
449
|
return {
|
|
455
450
|
interactiveFound: found.length,
|
|
456
451
|
stepsRun,
|
|
@@ -480,3 +475,4 @@ export async function crawl(session, opts, sleep) {
|
|
|
480
475
|
})(),
|
|
481
476
|
};
|
|
482
477
|
}
|
|
478
|
+
export { legitimatelyInert };
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import type { ReticleEvent } from '@reticlehq/core';
|
|
6
6
|
import type { FileSystemPort } from '../../memory/project/fs/fs-port.js';
|
|
7
7
|
import { type RawScriptCoverage } from './code-coverage.js';
|
|
8
|
-
import { type LevelReport } from './ledger.js';
|
|
8
|
+
import { type BehaviourFinding, type HolesReport, type LevelReport } from './ledger.js';
|
|
9
9
|
export interface AppCoverageReport {
|
|
10
10
|
levels: LevelReport[];
|
|
11
11
|
/** Writes whose failure path was driven and the app claimed success anyway. Findings, not gaps. */
|
|
@@ -16,6 +16,10 @@ export interface AppCoverageReport {
|
|
|
16
16
|
was: number;
|
|
17
17
|
now: number;
|
|
18
18
|
}[];
|
|
19
|
+
/** Every route, control, edge case, write, function and component never reached, by name. */
|
|
20
|
+
holes: HolesReport;
|
|
21
|
+
/** Behaviour faults the explorer found, one copy each. Absent when there are none. */
|
|
22
|
+
findings?: BehaviourFinding[];
|
|
19
23
|
note?: string;
|
|
20
24
|
}
|
|
21
25
|
export declare function foldAppCoverage(input: {
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* stands, level by level.
|
|
4
4
|
*/
|
|
5
5
|
import { emptyCodeCoverage, foldCodeCoverage } from './code-coverage.js';
|
|
6
|
-
import { LedgerStore, levelsOf, regressions } from './ledger.js';
|
|
6
|
+
import { holesOf, LedgerStore, levelsOf, regressions, } from './ledger.js';
|
|
7
7
|
import { sessionDelta } from './session-fold.js';
|
|
8
8
|
const NO_CODE_NOTE = 'The `executed` level is unmeasured here: code coverage needs a browser Reticle drives (`reticle drive`, or RETICLE_CDP_URL), and collection starts at the first coverage request, so drive and ask again. The other levels are real.';
|
|
9
9
|
export async function foldAppCoverage(input) {
|
|
@@ -26,6 +26,8 @@ export async function foldAppCoverage(input) {
|
|
|
26
26
|
levels: levelsOf(ledger),
|
|
27
27
|
unhandledWrites: ledger.writes.unhandled,
|
|
28
28
|
regressed: regressions(ledger),
|
|
29
|
+
holes: holesOf(ledger),
|
|
30
|
+
...(ledger.findings.length > 0 ? { findings: ledger.findings } : {}),
|
|
29
31
|
...(take === undefined && 0 === Object.keys(ledger.code).length ? { note: NO_CODE_NOTE } : {}),
|
|
30
32
|
};
|
|
31
33
|
}
|
|
@@ -22,8 +22,9 @@ export function appFileOf(url) {
|
|
|
22
22
|
catch {
|
|
23
23
|
return undefined;
|
|
24
24
|
}
|
|
25
|
-
if (NOT_APP.test(path) || !SOURCE_FILE.test(path))
|
|
25
|
+
if (NOT_APP.test(path) || !SOURCE_FILE.test(path)) {
|
|
26
26
|
return undefined;
|
|
27
|
+
}
|
|
27
28
|
return path.replace(/^\/+/, '');
|
|
28
29
|
}
|
|
29
30
|
export function emptyCodeCoverage() {
|
|
@@ -37,17 +38,20 @@ export function foldCodeCoverage(into, take) {
|
|
|
37
38
|
const out = { ...into };
|
|
38
39
|
for (const script of take) {
|
|
39
40
|
const file = appFileOf(script.url);
|
|
40
|
-
if (file === undefined)
|
|
41
|
+
if (file === undefined) {
|
|
41
42
|
continue;
|
|
43
|
+
}
|
|
42
44
|
const fns = { ...(out[file] ?? {}) };
|
|
43
45
|
for (const f of script.functions) {
|
|
44
46
|
const first = f.ranges[0];
|
|
45
|
-
if (first === undefined)
|
|
47
|
+
if (first === undefined) {
|
|
46
48
|
continue;
|
|
49
|
+
}
|
|
47
50
|
// The module's own top-level body is reported as an anonymous function starting at 0. It
|
|
48
51
|
// always runs when the file loads, so counting it would inflate every file by one.
|
|
49
|
-
if ('' === f.functionName && 0 === first.startOffset)
|
|
52
|
+
if ('' === f.functionName && 0 === first.startOffset) {
|
|
50
53
|
continue;
|
|
54
|
+
}
|
|
51
55
|
const key = `${f.functionName}@${String(first.startOffset)}`;
|
|
52
56
|
const ran = first.count > 0 || true === fns[key]?.executed;
|
|
53
57
|
fns[key] = { name: '' === f.functionName ? '(anonymous)' : f.functionName, executed: ran };
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Edge cases for the explorer to drive: the inputs and the interaction orders where behavioural
|
|
3
|
+
* bugs live.
|
|
4
|
+
*
|
|
5
|
+
* The explorer types one ordinary value into each field and activates each control once, in the
|
|
6
|
+
* order the page lists them. That proves the happy path and nothing else. A form that accepts
|
|
7
|
+
* " " as a name, keeps 11 in a field whose maximum is 10, or creates two orders on a double click
|
|
8
|
+
* fails on a value or an order nobody tried. This module lists those values and sequences.
|
|
9
|
+
*
|
|
10
|
+
* Pure and deterministic: the same field, control and clock give the same list in the same order.
|
|
11
|
+
* Every list is bounded, and the most telling items come first, so a small budget still tries them.
|
|
12
|
+
* A sequence says nothing about what a correct result is; `invariants.ts` judges the pages it leaves.
|
|
13
|
+
*/
|
|
14
|
+
import type { Step } from './explorer.js';
|
|
15
|
+
/** What a field takes, as far as its type, input mode, role and name say. */
|
|
16
|
+
export declare const FieldKind: {
|
|
17
|
+
readonly TEXT: "text";
|
|
18
|
+
readonly NUMBER: "number";
|
|
19
|
+
readonly EMAIL: "email";
|
|
20
|
+
readonly DATE: "date";
|
|
21
|
+
readonly TIME: "time";
|
|
22
|
+
readonly URL: "url";
|
|
23
|
+
readonly TEL: "tel";
|
|
24
|
+
};
|
|
25
|
+
export type FieldKind = (typeof FieldKind)[keyof typeof FieldKind];
|
|
26
|
+
/** Why a value is an edge case. The report shows this beside the value. */
|
|
27
|
+
export declare const EdgeLabel: {
|
|
28
|
+
readonly EMPTY: "empty";
|
|
29
|
+
readonly EMPTY_REQUIRED: "empty in a required field";
|
|
30
|
+
readonly WHITESPACE: "whitespace only";
|
|
31
|
+
readonly PADDED: "leading and trailing spaces";
|
|
32
|
+
readonly OVER_MAX_LENGTH: "one character over maxlength";
|
|
33
|
+
readonly UNDER_MIN_LENGTH: "one character under minlength";
|
|
34
|
+
readonly LONG_TEXT: "long text, no maxlength set";
|
|
35
|
+
readonly UNICODE: "emoji and non-Latin script";
|
|
36
|
+
readonly RTL: "right-to-left text";
|
|
37
|
+
readonly MARKUP: "markup characters";
|
|
38
|
+
readonly BREAKS_PATTERN: "does not match the pattern";
|
|
39
|
+
readonly DUPLICATE: "duplicate of a value the app already holds";
|
|
40
|
+
readonly ZERO: "zero";
|
|
41
|
+
readonly NEGATIVE: "negative number";
|
|
42
|
+
readonly BELOW_MIN: "one below min";
|
|
43
|
+
readonly ABOVE_MAX: "one above max";
|
|
44
|
+
readonly VERY_LARGE: "very large number";
|
|
45
|
+
readonly DECIMAL: "decimal number";
|
|
46
|
+
readonly WRONG_FORMAT: "wrong format for the field type";
|
|
47
|
+
readonly PAST_DATE: "date in the past";
|
|
48
|
+
readonly FUTURE_DATE: "date far in the future";
|
|
49
|
+
readonly IMPOSSIBLE_DATE: "date that does not exist";
|
|
50
|
+
readonly MIDNIGHT: "midnight";
|
|
51
|
+
readonly IMPOSSIBLE_TIME: "time that does not exist";
|
|
52
|
+
};
|
|
53
|
+
export type EdgeLabel = (typeof EdgeLabel)[keyof typeof EdgeLabel];
|
|
54
|
+
/** One field, with what the snapshot or the DOM gives about it. Every attribute is optional. */
|
|
55
|
+
export interface FieldSpec {
|
|
56
|
+
role: string;
|
|
57
|
+
name?: string;
|
|
58
|
+
type?: string;
|
|
59
|
+
inputMode?: string;
|
|
60
|
+
value?: string;
|
|
61
|
+
min?: string;
|
|
62
|
+
max?: string;
|
|
63
|
+
maxLength?: number;
|
|
64
|
+
minLength?: number;
|
|
65
|
+
pattern?: string;
|
|
66
|
+
required?: boolean;
|
|
67
|
+
/** Values the app already holds for this field, such as the rows of a list. */
|
|
68
|
+
existing?: readonly string[];
|
|
69
|
+
}
|
|
70
|
+
export interface EdgeValue {
|
|
71
|
+
value: string;
|
|
72
|
+
label: EdgeLabel;
|
|
73
|
+
}
|
|
74
|
+
/** The default bound. A field gets at most this many values. */
|
|
75
|
+
export declare const MAX_EDGE_VALUES = 16;
|
|
76
|
+
export declare function fieldKindOf(spec: Pick<FieldSpec, 'role' | 'type' | 'inputMode' | 'name'>): FieldKind;
|
|
77
|
+
/**
|
|
78
|
+
* The edge-case values for a field, most telling first, with no value twice, at most `max`.
|
|
79
|
+
* `now` is the injected clock: past and future dates are relative to it.
|
|
80
|
+
*/
|
|
81
|
+
export declare function edgeInputs(spec: FieldSpec, opts: {
|
|
82
|
+
now: () => number;
|
|
83
|
+
max?: number;
|
|
84
|
+
}): EdgeValue[];
|
|
85
|
+
/** The actions a sequence uses. `click` and `fill` are the explorer's own; the rest are new. */
|
|
86
|
+
export declare const EdgeAction: {
|
|
87
|
+
readonly CLICK: "click";
|
|
88
|
+
readonly FILL: "fill";
|
|
89
|
+
/** The browser's back. */
|
|
90
|
+
readonly BACK: "back";
|
|
91
|
+
/** A full reload of the current page. */
|
|
92
|
+
readonly RELOAD: "reload";
|
|
93
|
+
/** Wait until the page settles. Two steps with no settle between them run back to back. */
|
|
94
|
+
readonly SETTLE: "settle";
|
|
95
|
+
};
|
|
96
|
+
export type EdgeAction = (typeof EdgeAction)[keyof typeof EdgeAction];
|
|
97
|
+
export type EdgeStep = Step | {
|
|
98
|
+
action: typeof EdgeAction.BACK | typeof EdgeAction.RELOAD | typeof EdgeAction.SETTLE;
|
|
99
|
+
};
|
|
100
|
+
export declare const EdgeInteraction: {
|
|
101
|
+
readonly DOUBLE_ACTIVATION: "double-activation";
|
|
102
|
+
readonly RAPID_REPEAT: "rapid-repeat";
|
|
103
|
+
readonly ACT_THEN_BACK: "act-then-back";
|
|
104
|
+
readonly ACT_THEN_RELOAD: "act-then-reload";
|
|
105
|
+
readonly SUBMIT_TWICE: "submit-twice";
|
|
106
|
+
readonly SUBMIT_BEFORE_FILL: "submit-before-fill";
|
|
107
|
+
readonly OUT_OF_ORDER: "out-of-order";
|
|
108
|
+
};
|
|
109
|
+
export type EdgeInteraction = (typeof EdgeInteraction)[keyof typeof EdgeInteraction];
|
|
110
|
+
export interface EdgeSequence {
|
|
111
|
+
kind: EdgeInteraction;
|
|
112
|
+
/** The control the sequence is about. */
|
|
113
|
+
target: string;
|
|
114
|
+
steps: EdgeStep[];
|
|
115
|
+
}
|
|
116
|
+
/** How many activations a rapid repeat sends back to back. */
|
|
117
|
+
export declare const RAPID_REPEAT = 5;
|
|
118
|
+
/** The default bound on sequences for one control. */
|
|
119
|
+
export declare const MAX_EDGE_SEQUENCES = 8;
|
|
120
|
+
export declare function looksLikeSubmit(control: string): boolean;
|
|
121
|
+
/** A step in words, for the action sequence on a finding. */
|
|
122
|
+
export declare function describeStep(step: EdgeStep): string;
|
|
123
|
+
/**
|
|
124
|
+
* The edge sequences for `control`. `order` is the page's controls in document order; `fills` are
|
|
125
|
+
* the steps that fill the form the control belongs to, when it has one.
|
|
126
|
+
*/
|
|
127
|
+
export declare function edgeInteractions(input: {
|
|
128
|
+
control: string;
|
|
129
|
+
order: readonly string[];
|
|
130
|
+
fills?: readonly Step[];
|
|
131
|
+
}, opts?: {
|
|
132
|
+
max?: number;
|
|
133
|
+
}): EdgeSequence[];
|