@reticlehq/server 3.2.0 → 3.4.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-ENTERPRISE +1 -1
- package/README.md +3 -3
- package/SKILL.md +23 -22
- package/dist/chromium-launch-options.d.ts +4 -3
- package/dist/chromium-launch-options.js +6 -2
- package/dist/command/cli/adhoc-suite.d.ts +41 -0
- package/dist/command/cli/adhoc-suite.js +159 -0
- package/dist/command/cli/adhoc-verdict.d.ts +58 -2
- package/dist/command/cli/adhoc-verdict.js +108 -13
- package/dist/command/cli/answers/gate-hook-message.d.ts +6 -0
- package/dist/command/cli/answers/gate-hook-message.js +6 -0
- package/dist/command/cli/auth/cloud-keystore.d.ts +0 -2
- package/dist/command/cli/auth/cloud-keystore.js +0 -3
- package/dist/command/cli/auth/cloud-session.d.ts +4 -0
- package/dist/command/cli/auth/cloud-session.js +2 -0
- package/dist/command/cli/cli-doctor.js +30 -8
- package/dist/command/cli/cli-flow-commands.d.ts +3 -6
- package/dist/command/cli/cli-flow-commands.js +67 -4
- package/dist/command/cli/cli-parse-grammar.d.ts +7 -0
- package/dist/command/cli/cli-parse-grammar.js +7 -0
- package/dist/command/cli/cli-parse-verify.d.ts +2 -5
- package/dist/command/cli/cli-parse-verify.js +86 -13
- package/dist/command/cli/cli-parse.d.ts +14 -1
- package/dist/command/cli/cli-parse.js +54 -8
- package/dist/command/cli/cli-update-commands.js +8 -1
- package/dist/command/cli/cli-verify.d.ts +80 -0
- package/dist/command/cli/cli-verify.js +181 -26
- package/dist/command/cli/cloud-cli.js +5 -2
- package/dist/command/cli/cloud-kit.d.ts +0 -2
- package/dist/command/cli/cloud-kit.js +3 -5
- package/dist/command/cli/cloud-login.js +21 -3
- package/dist/command/cli/demo/demo-page.js +6 -3
- package/dist/command/cli/doctor/browser/chromium-hint.d.ts +5 -0
- package/dist/command/cli/doctor/browser/chromium-hint.js +5 -0
- package/dist/command/cli/doctor/doctor-sessions-line.d.ts +2 -1
- package/dist/command/cli/launch/cli-launch.d.ts +9 -29
- package/dist/command/cli/launch/cli-launch.js +27 -49
- package/dist/command/cli/launch/sdk-version-match.d.ts +84 -0
- package/dist/command/cli/launch/sdk-version-match.js +206 -0
- package/dist/command/cli/lifecycle/daemon-lifecycle.js +2 -16
- package/dist/command/cli/ports/resolve/cli-port.d.ts +38 -0
- package/dist/command/cli/ports/resolve/cli-port.js +169 -15
- package/dist/command/cli/report-command.d.ts +16 -0
- package/dist/command/cli/report-command.js +97 -0
- package/dist/command/cli/setup-mcp-cli.js +13 -13
- package/dist/command/cli/tutorial.d.ts +2 -2
- package/dist/command/cli/tutorial.js +21 -7
- package/dist/command/cli.js +67 -10
- package/dist/command/daemon/binding/daemon-status-probe.d.ts +44 -2
- package/dist/command/daemon/binding/daemon-status-probe.js +79 -6
- package/dist/command/daemon/binding/port-presence.d.ts +1 -1
- package/dist/command/daemon/daemon-resolve.d.ts +9 -0
- package/dist/command/daemon/daemon-resolve.js +1 -1
- package/dist/command/daemon/idle-shutdown.d.ts +2 -1
- package/dist/command/daemon/lifetime/daemon-usefulness.d.ts +7 -1
- package/dist/command/daemon/lifetime/daemon-usefulness.js +30 -7
- package/dist/command/dev/stale-issue-guard.d.ts +3 -4
- package/dist/command/dev/stale-issue-guard.js +8 -8
- package/dist/command/setup/bringup/connect-lease.d.ts +17 -0
- package/dist/command/setup/bringup/connect-lease.js +38 -0
- package/dist/command/setup/bringup/dev-server-files.d.ts +68 -0
- package/dist/command/setup/bringup/dev-server-files.js +131 -0
- package/dist/command/setup/bringup/dev-server-log-cap.d.ts +35 -0
- package/dist/command/setup/bringup/dev-server-log-cap.js +121 -0
- package/dist/command/setup/bringup/ensure-daemon.d.ts +65 -4
- package/dist/command/setup/bringup/ensure-daemon.js +135 -16
- package/dist/command/setup/bringup/static-page-server.d.ts +35 -0
- package/dist/command/setup/bringup/static-page-server.js +151 -0
- package/dist/command/setup/desktop-shape.d.ts +4 -0
- package/dist/command/setup/desktop-shape.js +18 -2
- package/dist/command/setup/init/init-host.js +8 -0
- package/dist/command/setup/init/init-port.d.ts +15 -0
- package/dist/command/setup/init/init-port.js +44 -0
- package/dist/command/setup/init/init-runtime.d.ts +1 -1
- package/dist/command/setup/init/init-runtime.js +39 -2
- package/dist/command/setup/node-effects.d.ts +27 -14
- package/dist/command/setup/node-effects.js +80 -44
- package/dist/command/setup/probe/loopback-probe-urls.d.ts +7 -0
- package/dist/command/setup/probe/loopback-probe-urls.js +40 -0
- package/dist/command/setup/probe/page-probe.d.ts +13 -2
- package/dist/command/setup/probe/page-probe.js +18 -3
- package/dist/command/setup/run-setup.d.ts +26 -1
- package/dist/command/setup/run-setup.js +96 -13
- package/dist/command/setup/session-pick.d.ts +13 -0
- package/dist/command/setup/session-pick.js +20 -0
- package/dist/command/setup/setup-command.d.ts +2 -14
- package/dist/command/setup/setup-command.js +105 -31
- package/dist/command/setup/setup-mcp.js +3 -3
- package/dist/command/setup/terminal/wait-progress.d.ts +8 -0
- package/dist/command/setup/terminal/wait-progress.js +17 -0
- package/dist/features/crawl/crawl-empty.js +1 -1
- package/dist/features/crawl/crawl-tools.d.ts +6 -0
- package/dist/features/crawl/crawl-tools.js +57 -0
- package/dist/features/crawl/crawl.d.ts +2 -0
- package/dist/features/crawl/crawl.js +171 -156
- package/dist/features/exhaust/app-coverage.d.ts +33 -0
- package/dist/features/exhaust/app-coverage.js +31 -0
- package/dist/features/exhaust/code-coverage.d.ts +47 -0
- package/dist/features/exhaust/code-coverage.js +77 -0
- package/dist/features/exhaust/exhaust-run.d.ts +33 -0
- package/dist/features/exhaust/exhaust-run.js +82 -0
- package/dist/features/exhaust/explorer.d.ts +103 -0
- package/dist/features/exhaust/explorer.js +195 -0
- package/dist/features/exhaust/failure-branches.d.ts +33 -0
- package/dist/features/exhaust/failure-branches.js +92 -0
- package/dist/features/exhaust/ledger.d.ts +202 -0
- package/dist/features/exhaust/ledger.js +252 -0
- package/dist/features/exhaust/session-fold.d.ts +31 -0
- package/dist/features/exhaust/session-fold.js +79 -0
- package/dist/features/exhaust/session-port.d.ts +36 -0
- package/dist/features/exhaust/session-port.js +102 -0
- package/dist/features/harness/drive-report.d.ts +0 -9
- package/dist/features/harness/drive-report.js +18 -0
- package/dist/features/harness/driver.js +4 -7
- package/dist/features/harness/fill-values.d.ts +6 -0
- package/dist/features/harness/fill-values.js +3 -0
- package/dist/features/harness/jev-driver.js +36 -22
- package/dist/features/harness/openai-driver.js +4 -7
- package/dist/features/harness/platform-config.d.ts +10 -0
- package/dist/features/harness/platform-config.js +13 -6
- package/dist/index.d.ts +10 -1
- package/dist/index.js +78 -104
- package/dist/judgement/capsule/capsule-store.d.ts +31 -2
- package/dist/judgement/capsule/capsule-store.js +58 -6
- package/dist/judgement/capsule/capsule.js +5 -0
- package/dist/judgement/capsule/causal-summary.d.ts +13 -1
- package/dist/judgement/capsule/causal-summary.js +7 -0
- package/dist/judgement/domain/domain-model.js +40 -9
- package/dist/judgement/oracles/self-instrument.js +4 -2
- package/dist/judgement/outcome/assertion-integrity.d.ts +3 -3
- package/dist/judgement/runs/artifact/gap-report.d.ts +17 -0
- package/dist/judgement/runs/artifact/gap-report.js +46 -0
- package/dist/judgement/runs/artifact/gap-summary.d.ts +58 -0
- package/dist/judgement/runs/artifact/gap-summary.js +85 -0
- package/dist/judgement/runs/artifact/render-report.js +4 -1
- package/dist/judgement/runs/artifact/replay-mapping.d.ts +2 -2
- package/dist/judgement/runs/artifact/replay-mapping.js +44 -9
- package/dist/judgement/runs/artifact/to-artifact.d.ts +7 -2
- package/dist/judgement/runs/artifact/to-artifact.js +1 -1
- package/dist/judgement/runs/context-tools.js +21 -3
- package/dist/judgement/runs/drive-run.js +7 -2
- package/dist/judgement/runs/verification-sync.d.ts +3 -1
- package/dist/judgement/runs/verification-sync.js +1 -1
- package/dist/language/flows/annotate-notes/annotate.d.ts +0 -15
- package/dist/language/flows/annotate-notes/annotate.js +66 -18
- package/dist/language/flows/change/affected.d.ts +9 -0
- package/dist/language/flows/change/affected.js +35 -0
- package/dist/language/flows/change/gate.d.ts +20 -0
- package/dist/language/flows/change/gate.js +12 -1
- package/dist/language/flows/flow-anchor.d.ts +12 -0
- package/dist/language/flows/flow-anchor.js +20 -0
- package/dist/language/flows/flow-author.d.ts +7 -0
- package/dist/language/flows/flow-author.js +19 -0
- package/dist/language/flows/flow-expect-grammar.d.ts +2 -2
- package/dist/language/flows/flow-expect-grammar.js +92 -11
- package/dist/language/flows/flow-intent.js +1 -1
- package/dist/language/flows/flow-journey.d.ts +54 -0
- package/dist/language/flows/flow-journey.js +119 -0
- package/dist/language/flows/flow-learning.d.ts +3 -0
- package/dist/language/flows/flow-learning.js +39 -8
- package/dist/language/flows/flow-replay-run.d.ts +33 -12
- package/dist/language/flows/flow-replay-run.js +131 -52
- package/dist/language/flows/flow-replay-types.d.ts +9 -0
- package/dist/language/flows/flow-replay.d.ts +27 -12
- package/dist/language/flows/flow-replay.js +127 -111
- package/dist/language/flows/flow-result.d.ts +2 -0
- package/dist/language/flows/flow-result.js +25 -0
- package/dist/language/flows/flow-step-runners.d.ts +26 -10
- package/dist/language/flows/flow-step-runners.js +20 -4
- package/dist/language/flows/flow-success.d.ts +21 -6
- package/dist/language/flows/flow-success.js +50 -138
- package/dist/language/flows/flow-tools.js +17 -5
- package/dist/language/flows/flows.d.ts +18 -5
- package/dist/language/flows/flows.js +101 -16
- package/dist/language/flows/heal.d.ts +9 -1
- package/dist/language/flows/heal.js +24 -2
- package/dist/language/flows/learned-sources.d.ts +21 -0
- package/dist/language/flows/learned-sources.js +84 -0
- package/dist/language/flows/recording/recording-backtrack.d.ts +0 -7
- package/dist/language/flows/recording/recording-backtrack.js +1 -1
- package/dist/language/flows/recording/replay-halt.d.ts +2 -1
- package/dist/language/flows/recording/replay-halt.js +3 -1
- package/dist/language/flows/recording/tape/recordings.d.ts +31 -2
- package/dist/language/flows/recording/tape/recordings.js +55 -1
- package/dist/language/flows/replay.d.ts +19 -0
- package/dist/language/flows/replay.js +53 -13
- package/dist/language/flows/stores/annotation-store.d.ts +5 -5
- package/dist/language/flows/stores/assertion-tiers-store.js +2 -3
- package/dist/language/flows/stores/flake-store.js +2 -3
- package/dist/language/flows/suite/coverage.d.ts +1 -1
- package/dist/language/flows/suite/parallel-suite.d.ts +2 -1
- package/dist/language/flows/verify-change-tools.js +19 -2
- package/dist/launch-chromium.d.ts +80 -0
- package/dist/launch-chromium.js +218 -0
- package/dist/memory/cloud/cloud-config.d.ts +11 -2
- package/dist/memory/cloud/cloud-config.js +47 -7
- package/dist/memory/cloud/cloud-sync.d.ts +4 -3
- package/dist/memory/cloud/cloud-sync.js +7 -10
- package/dist/memory/cloud/harness-config.d.ts +2 -0
- package/dist/memory/cloud/harness-offer.js +5 -6
- package/dist/memory/cloud/harness-switch.js +5 -6
- package/dist/memory/cloud/sync-cycle.d.ts +78 -1
- package/dist/memory/cloud/sync-cycle.js +321 -53
- package/dist/memory/cloud/sync-daemon.d.ts +13 -0
- package/dist/memory/cloud/sync-daemon.js +45 -20
- package/dist/memory/cloud/sync-disk.js +27 -8
- package/dist/memory/impact/impact-store.js +6 -0
- package/dist/memory/intent/inline-intent.js +8 -3
- package/dist/memory/intent/intent-shard-store.d.ts +33 -47
- package/dist/memory/intent/intent-shard-store.js +252 -140
- package/dist/memory/intent/intent-shard.d.ts +0 -23
- package/dist/memory/intent/intent-shard.js +1 -11
- package/dist/memory/intent/intent-store.d.ts +13 -8
- package/dist/memory/intent/intent-store.js +46 -90
- package/dist/memory/intent/intent-tools.js +34 -23
- package/dist/memory/journal/ambient-store.js +2 -3
- package/dist/memory/journal/deviation-report.js +4 -1
- package/dist/memory/journal/deviation-service.js +10 -2
- package/dist/memory/journal/drive-flow.d.ts +19 -14
- package/dist/memory/journal/drive-flow.js +65 -48
- package/dist/memory/journal/envelope-store.js +2 -3
- package/dist/memory/journal/journal-recorder.d.ts +21 -0
- package/dist/memory/journal/on-disk/retain-policy.d.ts +34 -0
- package/dist/memory/journal/on-disk/retain-policy.js +44 -0
- package/dist/memory/journal/on-disk/retention.d.ts +14 -10
- package/dist/memory/journal/on-disk/retention.js +13 -12
- package/dist/memory/journal/on-disk/startup-maintenance.d.ts +42 -0
- package/dist/memory/journal/on-disk/startup-maintenance.js +14 -0
- package/dist/memory/journal/on-disk/workspace-gitignore.d.ts +0 -11
- package/dist/memory/journal/on-disk/workspace-gitignore.js +6 -45
- package/dist/memory/journal/on-disk/workspace-tiers.d.ts +52 -0
- package/dist/memory/journal/on-disk/workspace-tiers.js +90 -0
- package/dist/memory/journal/route-template.d.ts +22 -0
- package/dist/memory/journal/route-template.js +44 -0
- package/dist/memory/journal/session-end.d.ts +13 -2
- package/dist/memory/journal/session-end.js +88 -18
- package/dist/memory/journal/session-journal.d.ts +8 -1
- package/dist/memory/journal/session-journal.js +10 -0
- package/dist/memory/project/baselines.d.ts +1 -1
- package/dist/memory/project/dir/reticle-dir.d.ts +3 -1
- package/dist/memory/project/dir/reticle-dir.js +1 -0
- package/dist/memory/project/fs/fs-port.js +44 -5
- package/dist/memory/project/fs/write-atomic.d.ts +26 -0
- package/dist/memory/project/fs/write-atomic.js +48 -0
- package/dist/memory/project/project-tools.js +3 -1
- package/dist/memory/recall/prior/connection-memory.d.ts +6 -0
- package/dist/memory/recall/prior/connection-memory.js +8 -0
- package/dist/memory/recall/registered-projects.d.ts +2 -1
- package/dist/portal/bridge/auth-failure-reason.d.ts +1 -1
- package/dist/portal/bridge/auth-failure-reason.js +9 -4
- package/dist/portal/bridge/bridge.d.ts +8 -0
- package/dist/portal/bridge/bridge.js +43 -2
- package/dist/portal/input/js-coverage.d.ts +25 -0
- package/dist/portal/input/js-coverage.js +30 -0
- package/dist/portal/input/real-input.d.ts +11 -0
- package/dist/portal/input/real-input.js +31 -5
- package/dist/portal/pool/browser-pool.d.ts +10 -0
- package/dist/portal/pool/browser-pool.js +10 -0
- package/dist/portal/pool/playwright-launcher.d.ts +6 -0
- package/dist/portal/pool/playwright-launcher.js +21 -13
- package/dist/portal/pool/pool-contract.d.ts +2 -0
- package/dist/portal/pool/storage-seed.d.ts +2 -1
- package/dist/portal/pool/zero-install.d.ts +27 -0
- package/dist/portal/pool/zero-install.js +54 -0
- package/dist/portal/realm/web-realm.js +6 -0
- package/dist/portal/session/dev-server/dev-command.d.ts +2 -2
- package/dist/portal/session/dev-server/dev-command.js +30 -12
- package/dist/portal/session/dev-server/dev-server-probe.d.ts +2 -1
- package/dist/portal/session/dev-server/dev-server-probe.js +17 -1
- package/dist/portal/session/dev-server/route-status-probe.d.ts +24 -0
- package/dist/portal/session/dev-server/route-status-probe.js +57 -0
- package/dist/portal/session/dev-server/served-document.d.ts +21 -0
- package/dist/portal/session/dev-server/served-document.js +57 -0
- package/dist/portal/session/facts/background-traffic.d.ts +9 -0
- package/dist/portal/session/facts/background-traffic.js +25 -0
- package/dist/portal/session/facts/observed-state.d.ts +11 -0
- package/dist/portal/session/facts/observed-state.js +34 -0
- package/dist/portal/session/live-control-tools.js +48 -3
- package/dist/portal/session/no-session-diagnosis.d.ts +29 -0
- package/dist/portal/session/no-session-diagnosis.js +62 -5
- package/dist/portal/session/no-session-next-action.d.ts +13 -0
- package/dist/portal/session/no-session-next-action.js +48 -9
- package/dist/portal/session/no-session-watch.d.ts +18 -0
- package/dist/portal/session/no-session-watch.js +88 -2
- package/dist/portal/session/presence/attachment-history.d.ts +13 -2
- package/dist/portal/session/session-health.d.ts +61 -0
- package/dist/portal/session/session-health.js +43 -3
- package/dist/portal/session/session-info.d.ts +6 -0
- package/dist/portal/session/session-manager.d.ts +29 -6
- package/dist/portal/session/session-manager.js +42 -5
- package/dist/portal/session/session-reaper.d.ts +28 -0
- package/dist/portal/session/session-reaper.js +49 -1
- package/dist/portal/session/session-successor.d.ts +11 -0
- package/dist/portal/session/session-successor.js +14 -1
- package/dist/portal/session/session-verdict-facts.d.ts +28 -0
- package/dist/portal/session/session-verdict-facts.js +15 -0
- package/dist/portal/session/session.d.ts +10 -0
- package/dist/portal/session/session.js +34 -26
- package/dist/surface/http-server.d.ts +2 -1
- package/dist/surface/http-server.js +4 -2
- package/dist/surface/mcp/mcp-proxy.d.ts +2 -2
- package/dist/surface/mcp/mcp-proxy.js +5 -5
- package/dist/surface/mcp/mcp.js +4 -2
- package/dist/surface/mcp/proxy/proxy-daemon-probe.d.ts +13 -1
- package/dist/surface/mcp/proxy/proxy-daemon-probe.js +27 -2
- package/dist/surface/mcp/proxy-idle-exit.d.ts +2 -1
- package/dist/surface/mcp/server-instructions.js +21 -8
- package/dist/surface/tools/act/after-match.d.ts +17 -0
- package/dist/surface/tools/act/after-match.js +30 -0
- package/dist/surface/tools/act/already-true.d.ts +40 -0
- package/dist/surface/tools/act/already-true.js +36 -0
- package/dist/surface/tools/act/durable.d.ts +69 -0
- package/dist/surface/tools/act/durable.js +139 -0
- package/dist/surface/tools/act/navigation/navigate-arrival.d.ts +19 -6
- package/dist/surface/tools/act/navigation/navigate-arrival.js +60 -10
- package/dist/surface/tools/act/navigation/navigate-result.d.ts +1 -12
- package/dist/surface/tools/act/navigation/navigate-result.js +21 -3
- package/dist/surface/tools/act/proved-controls.d.ts +9 -0
- package/dist/surface/tools/act/proved-controls.js +11 -0
- package/dist/surface/tools/act/sequence-grade.d.ts +5 -0
- package/dist/surface/tools/act/sequence-grade.js +32 -1
- package/dist/surface/tools/act/settle-in-flight.d.ts +6 -2
- package/dist/surface/tools/act/settle-in-flight.js +29 -6
- package/dist/surface/tools/act-capsule.d.ts +7 -0
- package/dist/surface/tools/act-capsule.js +68 -8
- package/dist/surface/tools/act-sequence-tool.js +25 -8
- package/dist/surface/tools/act-tools.js +51 -74
- package/dist/surface/tools/args/numeric-bounds.d.ts +15 -2
- package/dist/surface/tools/args/numeric-bounds.js +30 -3
- package/dist/surface/tools/assert/assert-grade.js +26 -3
- package/dist/surface/tools/assert/assert-verdict.js +32 -6
- package/dist/surface/tools/browser-tools.js +17 -4
- package/dist/surface/tools/coverage-tools.js +27 -1
- package/dist/surface/tools/dynamic-tools.js +6 -0
- package/dist/surface/tools/error-recovery.js +4 -4
- package/dist/surface/tools/feedback-invite.js +6 -4
- package/dist/surface/tools/feedback-tools.d.ts +2 -1
- package/dist/surface/tools/feedback-tools.js +1 -1
- package/dist/surface/tools/harness-explore.d.ts +3 -2
- package/dist/surface/tools/harness-explore.js +7 -6
- package/dist/surface/tools/invoke-tool.js +17 -4
- package/dist/surface/tools/lease-hint.d.ts +8 -0
- package/dist/surface/tools/lease-hint.js +10 -0
- package/dist/surface/tools/lease-tools.d.ts +16 -28
- package/dist/surface/tools/lease-tools.js +68 -11
- package/dist/surface/tools/lineage-tools.js +1 -1
- package/dist/surface/tools/live-call-text.js +4 -1
- package/dist/surface/tools/observe-tools.js +23 -7
- package/dist/surface/tools/real-input-attempt.d.ts +0 -5
- package/dist/surface/tools/real-input-attempt.js +1 -1
- package/dist/surface/tools/tool-kit.d.ts +5 -0
- package/dist/surface/tools/tool-kit.js +6 -1
- package/dist/surface/tools/tool-surface.js +17 -5
- package/dist/surface/tools/tools.js +68 -29
- package/dist/telemetry/argument-shape.d.ts +0 -2
- package/dist/telemetry/argument-shape.js +0 -4
- package/dist/telemetry/error-class.d.ts +2 -3
- package/dist/telemetry/error-class.js +2 -3
- package/dist/telemetry/feedback-local.d.ts +1 -0
- package/dist/telemetry/feedback-local.js +1 -3
- package/dist/telemetry/feedback.d.ts +1 -1
- package/dist/telemetry/harness-drive.d.ts +0 -2
- package/dist/telemetry/harness-drive.js +0 -4
- package/dist/telemetry/hud-metrics.d.ts +32 -0
- package/dist/telemetry/hud-metrics.js +95 -0
- package/dist/telemetry/session-metrics.d.ts +5 -0
- package/dist/telemetry/session-metrics.js +15 -1
- package/dist/wire-journal.d.ts +34 -0
- package/dist/wire-journal.js +104 -0
- package/dist/workspace-packages.d.ts +0 -2
- package/dist/workspace-packages.js +1 -9
- package/docs/README.md +5 -2
- package/docs/actions.mdx +3 -3
- package/docs/agent-cheatsheet.md +4 -4
- package/docs/architecture.md +6 -6
- package/docs/best-practices.mdx +1 -1
- package/docs/capabilities.mdx +32 -1
- package/docs/cli/cloud.mdx +21 -9
- package/docs/cli/doctor.mdx +4 -0
- package/docs/cli/feedback.mdx +2 -2
- package/docs/cli/gate.mdx +9 -4
- package/docs/cli/init.mdx +1 -0
- package/docs/cli/report.mdx +44 -0
- package/docs/cli/update.mdx +10 -0
- package/docs/cli.mdx +9 -0
- package/docs/desktop.mdx +14 -10
- package/docs/environment.mdx +87 -0
- package/docs/faq.mdx +18 -16
- package/docs/first-drive.md +5 -13
- package/docs/flows.md +32 -2
- package/docs/frameworks.mdx +140 -9
- package/docs/getting-started.md +3 -3
- package/docs/http-transport.md +6 -3
- package/docs/human-control.md +2 -2
- package/docs/index.mdx +16 -6
- package/docs/install-agentic.mdx +37 -1
- package/docs/install-manual.mdx +1 -1
- package/docs/integration-patterns.md +3 -3
- package/docs/multi-agent-testing.md +1 -1
- package/docs/packages/babel-plugin.mdx +1 -1
- package/docs/packages/browser.mdx +2 -2
- package/docs/packages/react.mdx +2 -2
- package/docs/packages/tauri.mdx +1 -1
- package/docs/packages/vite-plugin.mdx +2 -2
- package/docs/packages.mdx +9 -9
- package/docs/predicates.mdx +176 -7
- package/docs/quickstart.mdx +2 -2
- package/docs/recipes.mdx +2 -2
- package/docs/state-management.mdx +2 -2
- package/docs/system-map.md +1 -1
- package/docs/telemetry.md +1 -1
- package/docs/token-efficiency.md +3 -3
- package/docs/tools/act-and-wait.mdx +1 -0
- package/docs/tools/act-sequence.mdx +1 -1
- package/docs/tools/console.mdx +1 -1
- package/docs/tools/inspect.mdx +1 -1
- package/docs/tools/network.mdx +1 -1
- package/docs/tools/query.mdx +11 -11
- package/docs/tools/sessions.mdx +1 -1
- package/docs/tools/snapshot.mdx +1 -1
- package/docs/tools/state.mdx +1 -1
- package/docs/tools/wait-for.mdx +1 -1
- package/docs/usage.md +8 -7
- package/docs/use-cases.mdx +157 -0
- package/docs/vs-screenshots.mdx +3 -3
- package/docs/what-is-recorded.md +62 -0
- package/docs/what-is-reticle.mdx +17 -20
- package/docs/why-reticle.mdx +14 -21
- package/package.json +11 -10
- package/dist/judgement/capsule/minimize.d.ts +0 -12
- package/dist/judgement/capsule/minimize.js +0 -22
- package/dist/judgement/outcome/predicate-to-expect.d.ts +0 -44
- package/dist/judgement/outcome/predicate-to-expect.js +0 -148
- package/dist/judgement/phenomena/phenomena.d.ts +0 -26
- package/dist/judgement/phenomena/phenomena.js +0 -101
- package/dist/language/flows/flow-report.d.ts +0 -22
- package/dist/language/flows/flow-report.js +0 -106
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AnnotationErrorCode, AnnotationKind, AnnotationTarget, COMPILED_PREDICATE_PREFIX, flowExpectHasConsequence, flowExpectIsPresenceOnly, } from '@reticlehq/core';
|
|
1
|
+
import { AnnotationErrorCode, AnnotationKind, AnnotationTarget, COMPILED_PREDICATE_PREFIX, flowExpectHasConsequence, flowExpectIsPresenceOnly, PredicateKind, } from '@reticlehq/core';
|
|
2
2
|
/**
|
|
3
3
|
* The PURE compiler at the heart of the annotation facet. A structured annotation +
|
|
4
4
|
* the current captured-step count → an AnnotateOutcome (a result envelope + the patch to apply to
|
|
@@ -14,35 +14,71 @@ import { AnnotationErrorCode, AnnotationKind, AnnotationTarget, COMPILED_PREDICA
|
|
|
14
14
|
* compilation is explicitly FUTURE — an NL string never reaches here (AnnotationSchema
|
|
15
15
|
* rejects it upstream; the tool maps that to UNKNOWN_KIND). No NL parser exists or is faked.
|
|
16
16
|
*/
|
|
17
|
+
/**
|
|
18
|
+
* A network annotation as a predicate, `count` included.
|
|
19
|
+
*
|
|
20
|
+
* Written once because it is compiled on two paths — a step gate and a flow's success oracle — and
|
|
21
|
+
* the first version of this migration wrote it twice and dropped `count` from both. The schema's own
|
|
22
|
+
* comment says why that matters: presence says the request fired, cardinality catches the
|
|
23
|
+
* double-submit that fired it twice, and `count` is the point of the annotation.
|
|
24
|
+
*
|
|
25
|
+
* A counted assertion also gates on `settled`. A wait-until-true evaluator satisfies `count: 1` the
|
|
26
|
+
* instant the FIRST request lands, which is before a duplicate arrives — so without the gate the
|
|
27
|
+
* guard passes on exactly the bug it exists to catch.
|
|
28
|
+
*/
|
|
29
|
+
function netPredicate(net) {
|
|
30
|
+
const clause = {
|
|
31
|
+
kind: PredicateKind.NET,
|
|
32
|
+
};
|
|
33
|
+
if (net.method !== undefined)
|
|
34
|
+
clause.method = net.method;
|
|
35
|
+
if (net.urlContains !== undefined)
|
|
36
|
+
clause.urlContains = net.urlContains;
|
|
37
|
+
if (net.status !== undefined)
|
|
38
|
+
clause.status = net.status;
|
|
39
|
+
if (net.count === undefined)
|
|
40
|
+
return clause;
|
|
41
|
+
clause.count = net.count;
|
|
42
|
+
return { kind: PredicateKind.ALL_OF, predicates: [{ kind: PredicateKind.SETTLED }, clause] };
|
|
43
|
+
}
|
|
17
44
|
export function compileAnnotation(a, stepCount) {
|
|
18
45
|
switch (a.kind) {
|
|
19
46
|
case AnnotationKind.ASSERT_SIGNAL: {
|
|
20
47
|
if (0 === stepCount)
|
|
21
48
|
return noStep();
|
|
22
|
-
const
|
|
49
|
+
const signal = {
|
|
50
|
+
kind: PredicateKind.SIGNAL,
|
|
51
|
+
name: a.name,
|
|
52
|
+
};
|
|
23
53
|
if (a.dataMatches !== undefined)
|
|
24
|
-
|
|
25
|
-
return stepPatch(a, stepCount,
|
|
54
|
+
signal.dataMatches = a.dataMatches;
|
|
55
|
+
return stepPatch(a, stepCount, signal);
|
|
26
56
|
}
|
|
27
57
|
case AnnotationKind.ASSERT_VISIBLE: {
|
|
28
58
|
if (0 === stepCount)
|
|
29
59
|
return noStep();
|
|
30
|
-
return stepPatch(a, stepCount, {
|
|
60
|
+
return stepPatch(a, stepCount, {
|
|
61
|
+
kind: PredicateKind.ELEMENT,
|
|
62
|
+
query: { testid: a.testid },
|
|
63
|
+
});
|
|
31
64
|
}
|
|
32
65
|
case AnnotationKind.ASSERT_STATE: {
|
|
33
66
|
if (0 === stepCount)
|
|
34
67
|
return noStep();
|
|
35
|
-
const state = {
|
|
68
|
+
const state = {
|
|
69
|
+
kind: PredicateKind.STATE,
|
|
70
|
+
path: a.statePath,
|
|
71
|
+
};
|
|
36
72
|
if (a.store !== undefined)
|
|
37
73
|
state.store = a.store;
|
|
38
74
|
if (a.equals !== undefined)
|
|
39
75
|
state.equals = a.equals;
|
|
40
|
-
return stepPatch(a, stepCount,
|
|
76
|
+
return stepPatch(a, stepCount, state);
|
|
41
77
|
}
|
|
42
78
|
case AnnotationKind.ASSERT_NET: {
|
|
43
79
|
if (0 === stepCount)
|
|
44
80
|
return noStep();
|
|
45
|
-
return stepPatch(a, stepCount,
|
|
81
|
+
return stepPatch(a, stepCount, netPredicate(a.net));
|
|
46
82
|
}
|
|
47
83
|
case AnnotationKind.MARK_DYNAMIC:
|
|
48
84
|
return {
|
|
@@ -53,39 +89,51 @@ export function compileAnnotation(a, stepCount) {
|
|
|
53
89
|
// Precedence: signal > state > net > console > testid (a consequence end-condition beats a
|
|
54
90
|
// presence check). None of them → MISSING_FIELD.
|
|
55
91
|
if (a.signal !== undefined) {
|
|
56
|
-
return flowSuccess(a, {
|
|
92
|
+
return flowSuccess(a, { kind: PredicateKind.SIGNAL, name: a.signal });
|
|
57
93
|
}
|
|
58
94
|
if (a.statePath !== undefined) {
|
|
59
|
-
const state = {
|
|
95
|
+
const state = {
|
|
96
|
+
kind: PredicateKind.STATE,
|
|
97
|
+
path: a.statePath,
|
|
98
|
+
};
|
|
60
99
|
if (a.store !== undefined)
|
|
61
100
|
state.store = a.store;
|
|
62
101
|
if (a.equals !== undefined)
|
|
63
102
|
state.equals = a.equals;
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
return flowSuccess(a,
|
|
103
|
+
// `hold` was a v1 flag meaning "this must STILL be true after the page settles". A predicate
|
|
104
|
+
// says that by composing: the settle gate is a clause, not a boolean on another clause.
|
|
105
|
+
return flowSuccess(a, true === a.hold
|
|
106
|
+
? { kind: PredicateKind.ALL_OF, predicates: [{ kind: PredicateKind.SETTLED }, state] }
|
|
107
|
+
: state);
|
|
67
108
|
}
|
|
68
109
|
if (a.net !== undefined) {
|
|
69
|
-
return flowSuccess(a,
|
|
110
|
+
return flowSuccess(a, netPredicate(a.net));
|
|
70
111
|
}
|
|
71
112
|
if (a.console !== undefined) {
|
|
72
|
-
return flowSuccess(a, {
|
|
113
|
+
return flowSuccess(a, {
|
|
114
|
+
kind: PredicateKind.CONSOLE,
|
|
115
|
+
...(a.console.level === undefined ? {} : { level: a.console.level }),
|
|
116
|
+
...(a.console.absent === undefined ? {} : { absent: a.console.absent }),
|
|
117
|
+
});
|
|
73
118
|
}
|
|
74
119
|
if (a.testid !== undefined) {
|
|
75
|
-
return flowSuccess(a, {
|
|
120
|
+
return flowSuccess(a, { kind: PredicateKind.ELEMENT, query: { testid: a.testid } });
|
|
76
121
|
}
|
|
77
122
|
// Last, and above only MISSING_FIELD. An element is a locator and text is content; where a
|
|
78
123
|
// caller offered both, the locator is the more stable anchor. But text beats nothing at all,
|
|
79
124
|
// which is what an app whose only observable is a rendered value used to get (#811).
|
|
80
125
|
if (a.text !== undefined) {
|
|
81
|
-
const text = {
|
|
126
|
+
const text = {
|
|
127
|
+
kind: PredicateKind.TEXT,
|
|
128
|
+
contains: a.text.contains,
|
|
129
|
+
};
|
|
82
130
|
if (a.text.scope !== undefined)
|
|
83
131
|
text.scope = a.text.scope;
|
|
84
132
|
if (a.text.absent !== undefined)
|
|
85
133
|
text.absent = a.text.absent;
|
|
86
134
|
if (a.text.visible !== undefined)
|
|
87
135
|
text.visible = a.text.visible;
|
|
88
|
-
return flowSuccess(a,
|
|
136
|
+
return flowSuccess(a, text);
|
|
89
137
|
}
|
|
90
138
|
return { result: { ok: false, code: AnnotationErrorCode.MISSING_FIELD } };
|
|
91
139
|
}
|
|
@@ -22,3 +22,12 @@ export interface AffectedResult {
|
|
|
22
22
|
* a repo-relative change matches an absolute stamp and vice-versa.
|
|
23
23
|
*/
|
|
24
24
|
export declare function affectedFlows(flows: readonly FlowSources[], changedFiles: readonly string[]): AffectedResult;
|
|
25
|
+
export declare function isInteractiveSource(text: string): boolean;
|
|
26
|
+
/**
|
|
27
|
+
* Changed interactive components that no saved flow's manifest names.
|
|
28
|
+
*
|
|
29
|
+
* `affectedFlows` starts from the flows, so a file no flow ever touched affects nothing — and a gate
|
|
30
|
+
* built only on it passed a brand-new feature that had never been driven. This starts from the
|
|
31
|
+
* change instead. `isInteractive` is injected so the decision stays pure; the CLI reads the file.
|
|
32
|
+
*/
|
|
33
|
+
export declare function unflowedFiles(flows: readonly FlowSources[], changedFiles: readonly string[], isInteractive: (file: string) => boolean): string[];
|
|
@@ -33,3 +33,38 @@ export function affectedFlows(flows, changedFiles) {
|
|
|
33
33
|
}
|
|
34
34
|
return { affected, unknownProvenance };
|
|
35
35
|
}
|
|
36
|
+
/** A file a source stamper can put on an element — the only files a flow's manifest can ever name. */
|
|
37
|
+
const COMPONENT_FILE = /\.(?:jsx|tsx|vue|svelte|astro)$/;
|
|
38
|
+
/** Same test-file rule the stampers use, so a test is never demanded a flow. */
|
|
39
|
+
const TEST_FILE = /(?:\.(?:test|spec)\.[^./]+$)|(?:^|\/)__tests__\//;
|
|
40
|
+
/**
|
|
41
|
+
* Something a step could act on: a handler prop (`onClick=`, `@click`, `v-on:`, `on:submit`) or a
|
|
42
|
+
* native control.
|
|
43
|
+
*
|
|
44
|
+
* ponytail: a text heuristic, not a parse. A flow's manifest only names the file of the element a
|
|
45
|
+
* step ACTED on, so a display-only component can never be attributed and demanding a flow for one
|
|
46
|
+
* would block forever. A handler passed in from a parent under another name escapes it; upgrade to
|
|
47
|
+
* recording the source of the element an `expect` matched if that proves common.
|
|
48
|
+
*/
|
|
49
|
+
const INTERACTIVE = /\bon[A-Z]\w*=|@[a-z]+[=.]|\bv-on:|\bon:[a-z]+|<(?:button|input|select|textarea|form|a|summary)[\s>/]/;
|
|
50
|
+
export function isInteractiveSource(text) {
|
|
51
|
+
return INTERACTIVE.test(text);
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Changed interactive components that no saved flow's manifest names.
|
|
55
|
+
*
|
|
56
|
+
* `affectedFlows` starts from the flows, so a file no flow ever touched affects nothing — and a gate
|
|
57
|
+
* built only on it passed a brand-new feature that had never been driven. This starts from the
|
|
58
|
+
* change instead. `isInteractive` is injected so the decision stays pure; the CLI reads the file.
|
|
59
|
+
*/
|
|
60
|
+
export function unflowedFiles(flows, changedFiles, isInteractive) {
|
|
61
|
+
const sources = flows.flatMap((flow) => (flow.sources ?? []).map(normalize));
|
|
62
|
+
return changedFiles.filter((file) => {
|
|
63
|
+
const c = normalize(file);
|
|
64
|
+
if (!COMPONENT_FILE.test(c) || TEST_FILE.test(c))
|
|
65
|
+
return false;
|
|
66
|
+
if (sources.some((s) => c === s || c.endsWith(`/${s}`) || s.endsWith(`/${c}`)))
|
|
67
|
+
return false;
|
|
68
|
+
return isInteractive(file);
|
|
69
|
+
});
|
|
70
|
+
}
|
|
@@ -22,6 +22,16 @@ interface GateInput {
|
|
|
22
22
|
downgraded?: readonly DowngradedFlow[];
|
|
23
23
|
/** Flows that covered a changed file but no longer exist — coverage deleted rather than satisfied. */
|
|
24
24
|
deleted?: readonly string[];
|
|
25
|
+
/** Changed interactive components no saved flow touches — a change nothing has driven. */
|
|
26
|
+
unflowed?: readonly string[];
|
|
27
|
+
/** Coverage levels below the best they ever reached — something new appeared, nothing covered it. */
|
|
28
|
+
coverageRegressed?: readonly {
|
|
29
|
+
level: string;
|
|
30
|
+
was: number;
|
|
31
|
+
now: number;
|
|
32
|
+
}[];
|
|
33
|
+
/** Changed files the browser loaded and never ran a function of. */
|
|
34
|
+
unexecuted?: readonly string[];
|
|
25
35
|
}
|
|
26
36
|
interface GateResult {
|
|
27
37
|
/** True when every affected, non-flaky flow has a passing artifact AND nothing was weakened/deleted. */
|
|
@@ -34,6 +44,16 @@ interface GateResult {
|
|
|
34
44
|
downgraded: DowngradedFlow[];
|
|
35
45
|
/** Deleted flows that covered changed files — BLOCKING for the same reason. */
|
|
36
46
|
deleted: string[];
|
|
47
|
+
/** Changed interactive components no flow covers — BLOCKING: the change was never driven. */
|
|
48
|
+
unflowed: string[];
|
|
49
|
+
/** BLOCKING: the app grew something nothing has covered. */
|
|
50
|
+
coverageRegressed: {
|
|
51
|
+
level: string;
|
|
52
|
+
was: number;
|
|
53
|
+
now: number;
|
|
54
|
+
}[];
|
|
55
|
+
/** BLOCKING: changed code that never ran in any drive. */
|
|
56
|
+
unexecuted: string[];
|
|
37
57
|
}
|
|
38
58
|
export declare function gateDecision(input: GateInput): GateResult;
|
|
39
59
|
export {};
|
|
@@ -23,11 +23,22 @@ export function gateDecision(input) {
|
|
|
23
23
|
// trivially gameable, which is the exact failure this gate exists to prevent.
|
|
24
24
|
const downgraded = [...(input.downgraded ?? [])];
|
|
25
25
|
const deleted = [...(input.deleted ?? [])];
|
|
26
|
+
const unflowed = [...(input.unflowed ?? [])];
|
|
27
|
+
const coverageRegressed = [...(input.coverageRegressed ?? [])];
|
|
28
|
+
const unexecuted = [...(input.unexecuted ?? [])];
|
|
26
29
|
return {
|
|
27
|
-
pass: 0 === uncovered.length &&
|
|
30
|
+
pass: 0 === uncovered.length &&
|
|
31
|
+
0 === downgraded.length &&
|
|
32
|
+
0 === deleted.length &&
|
|
33
|
+
0 === unflowed.length &&
|
|
34
|
+
0 === coverageRegressed.length &&
|
|
35
|
+
0 === unexecuted.length,
|
|
28
36
|
uncovered,
|
|
29
37
|
quarantined,
|
|
30
38
|
downgraded,
|
|
31
39
|
deleted,
|
|
40
|
+
unflowed,
|
|
41
|
+
coverageRegressed,
|
|
42
|
+
unexecuted,
|
|
32
43
|
};
|
|
33
44
|
}
|
|
@@ -28,6 +28,18 @@ export declare function nearestTestid(missing: string, present: string[]): strin
|
|
|
28
28
|
export declare function nearestIsAmbiguous(missing: string, present: string[]): boolean;
|
|
29
29
|
/** Build the legible-drift record for a testid anchor that resolved to zero live elements. */
|
|
30
30
|
export declare function testidDrift(value: string, hint: QueryEmptyHint | undefined): Drift;
|
|
31
|
+
/**
|
|
32
|
+
* Build the drift for a step whose anchor matched MORE THAN ONE live element.
|
|
33
|
+
*
|
|
34
|
+
* `ambiguous: true` is set because that is exactly what it is, and it is the field heal already
|
|
35
|
+
* reads to refuse an auto-rebind -- an ambiguous drift must never be healed to an arbitrary pick,
|
|
36
|
+
* which is the same reasoning that made this a drift instead of a note in the first place.
|
|
37
|
+
*
|
|
38
|
+
* `nearest` is null on purpose. There is no nearest: the anchor was found, several times over, and
|
|
39
|
+
* proposing one of the matches as "the" match would re-make the guess this reason exists to stop.
|
|
40
|
+
* The fix is a NARROWER anchor, and only a human or an agent looking at the page can choose it.
|
|
41
|
+
*/
|
|
42
|
+
export declare function ambiguousAnchorDrift(value: string, matches: number): Drift;
|
|
31
43
|
/**
|
|
32
44
|
* Build the drift for a step whose `expect.element` testid was absent after the action ran.
|
|
33
45
|
*
|
|
@@ -139,6 +139,26 @@ export function testidDrift(value, hint) {
|
|
|
139
139
|
drift.ambiguous = true;
|
|
140
140
|
return drift;
|
|
141
141
|
}
|
|
142
|
+
/**
|
|
143
|
+
* Build the drift for a step whose anchor matched MORE THAN ONE live element.
|
|
144
|
+
*
|
|
145
|
+
* `ambiguous: true` is set because that is exactly what it is, and it is the field heal already
|
|
146
|
+
* reads to refuse an auto-rebind -- an ambiguous drift must never be healed to an arbitrary pick,
|
|
147
|
+
* which is the same reasoning that made this a drift instead of a note in the first place.
|
|
148
|
+
*
|
|
149
|
+
* `nearest` is null on purpose. There is no nearest: the anchor was found, several times over, and
|
|
150
|
+
* proposing one of the matches as "the" match would re-make the guess this reason exists to stop.
|
|
151
|
+
* The fix is a NARROWER anchor, and only a human or an agent looking at the page can choose it.
|
|
152
|
+
*/
|
|
153
|
+
export function ambiguousAnchorDrift(value, matches) {
|
|
154
|
+
return {
|
|
155
|
+
reasonKind: DriftReason.ANCHOR_AMBIGUOUS,
|
|
156
|
+
reason: `testid "${value}" matched ${String(matches)} live elements — which one the recording meant is not decidable; anchor this step to something unique`,
|
|
157
|
+
anchor: value,
|
|
158
|
+
nearest: null,
|
|
159
|
+
ambiguous: true,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
142
162
|
/**
|
|
143
163
|
* Build the drift for a step whose `expect.element` testid was absent after the action ran.
|
|
144
164
|
*
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { FlowFile } from '@reticlehq/core';
|
|
2
|
+
/**
|
|
3
|
+
* Who is making a flow right now: the agent that announced itself over MCP, and the person signed in
|
|
4
|
+
* to Reticle on this machine. A key handed over in the environment names no person, so none is
|
|
5
|
+
* written — the cloud can still attribute the push by the key that sent it.
|
|
6
|
+
*/
|
|
7
|
+
export declare function flowAuthor(env?: NodeJS.ProcessEnv, home?: string): FlowFile['author'];
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { homedir } from 'node:os';
|
|
2
|
+
import { mcpClientIdentity } from '../../surface/mcp/peer/client-identity.js';
|
|
3
|
+
import { readAccountState } from '../../memory/cloud/account-state.js';
|
|
4
|
+
/**
|
|
5
|
+
* Who is making a flow right now: the agent that announced itself over MCP, and the person signed in
|
|
6
|
+
* to Reticle on this machine. A key handed over in the environment names no person, so none is
|
|
7
|
+
* written — the cloud can still attribute the push by the key that sent it.
|
|
8
|
+
*/
|
|
9
|
+
export function flowAuthor(env = process.env, home = homedir()) {
|
|
10
|
+
const agent = mcpClientIdentity().name;
|
|
11
|
+
const account = readAccountState(home, env);
|
|
12
|
+
const person = account.signedIn ? account.email : undefined;
|
|
13
|
+
if (agent === undefined && person === undefined)
|
|
14
|
+
return undefined;
|
|
15
|
+
return {
|
|
16
|
+
...(agent === undefined ? {} : { agent }),
|
|
17
|
+
...(person === undefined ? {} : { person }),
|
|
18
|
+
};
|
|
19
|
+
}
|
|
@@ -18,9 +18,8 @@ export declare const FlowParseNote: {
|
|
|
18
18
|
readonly NOT_JSON: "flow file is not valid JSON — fix the syntax or regenerate it with reticle_flow_save";
|
|
19
19
|
readonly MALFORMED: "flow file is malformed — fix or regenerate it with reticle_flow_save";
|
|
20
20
|
readonly UNSUPPORTED_SHAPE: "valid JSON, unsupported expect shape";
|
|
21
|
-
readonly UNENFORCED: "valid JSON, but this expect uses a predicate kind a saved flow cannot enforce (settled, route, animation, anyOf, not)";
|
|
22
21
|
};
|
|
23
|
-
|
|
22
|
+
type CoerceExpectResult = {
|
|
24
23
|
ok: true;
|
|
25
24
|
value: unknown;
|
|
26
25
|
} | {
|
|
@@ -31,3 +30,4 @@ export declare function coerceFlowExpect(raw: unknown): CoerceExpectResult;
|
|
|
31
30
|
export declare function coerceFlowFileExpects(raw: unknown): CoerceExpectResult;
|
|
32
31
|
export declare function describeFlowZodFailure(error: ZodError): string;
|
|
33
32
|
export declare function parseFlowFileText(text: string): FlowResult<FlowFile>;
|
|
33
|
+
export {};
|
|
@@ -11,14 +11,11 @@
|
|
|
11
11
|
* re-save writes the canonical shape. A kind a saved flow cannot enforce is refused, not stripped
|
|
12
12
|
* to an empty expect that would grade asserted-while-unchecked.
|
|
13
13
|
*/
|
|
14
|
-
import { FlowErrorCode, FlowFileSchema } from '@reticlehq/core';
|
|
15
|
-
import { PredicateSchema } from '@reticlehq/engine/question/predicate/predicate.js';
|
|
16
|
-
import { predicateToExpect } from '../../judgement/outcome/predicate-to-expect.js';
|
|
14
|
+
import { FlowExpectSchema, FlowPredicateSchema, READABLE_FLOW_VERSIONS, FlowErrorCode, FlowFileSchema, } from '@reticlehq/core';
|
|
17
15
|
export const FlowParseNote = {
|
|
18
16
|
NOT_JSON: 'flow file is not valid JSON — fix the syntax or regenerate it with reticle_flow_save',
|
|
19
17
|
MALFORMED: 'flow file is malformed — fix or regenerate it with reticle_flow_save',
|
|
20
18
|
UNSUPPORTED_SHAPE: 'valid JSON, unsupported expect shape',
|
|
21
|
-
UNENFORCED: 'valid JSON, but this expect uses a predicate kind a saved flow cannot enforce (settled, route, animation, anyOf, not)',
|
|
22
19
|
};
|
|
23
20
|
function isRecord(value) {
|
|
24
21
|
return 'object' === typeof value && null !== value && !Array.isArray(value);
|
|
@@ -43,20 +40,44 @@ function flattenSignalObject(raw) {
|
|
|
43
40
|
}
|
|
44
41
|
export function coerceFlowExpect(raw) {
|
|
45
42
|
if (isRecord(raw) && 'kind' in raw) {
|
|
46
|
-
const parsed =
|
|
43
|
+
const parsed = FlowPredicateSchema.safeParse(raw);
|
|
47
44
|
if (!parsed.success) {
|
|
48
45
|
return { ok: false, detail: describePredicateExpectFailure(parsed.error) };
|
|
49
46
|
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
return { ok: true, value: expect };
|
|
47
|
+
// Kept AS the predicate. There is nothing to convert to any more, so nothing to refuse for
|
|
48
|
+
// being inexpressible — the shape an agent wrote is the shape the file holds.
|
|
49
|
+
return { ok: true, value: parsed.data };
|
|
54
50
|
}
|
|
55
51
|
if (!isRecord(raw))
|
|
56
52
|
return { ok: true, value: raw };
|
|
57
|
-
|
|
53
|
+
const flattened = flattenSignalObject(raw);
|
|
54
|
+
/*
|
|
55
|
+
* A v1 expect is validated HERE so a misspelling is named.
|
|
56
|
+
*
|
|
57
|
+
* The schema accepts both formats as a union, and a union rejection reports every branch's
|
|
58
|
+
* complaint at once — so `{ signal: 'x', signl: 'typo' }` came back as "flow file is malformed"
|
|
59
|
+
* without the word `signl` in it. The file was still refused, which is the half that matters, but
|
|
60
|
+
* the reader was left to find a one-character typo by eye. Checking the v1 shape on its own, on
|
|
61
|
+
* the path that already knows this is a v1 expect, puts the key back in the message.
|
|
62
|
+
*/
|
|
63
|
+
const asV1 = FlowExpectSchema.safeParse(flattened);
|
|
64
|
+
if (!asV1.success) {
|
|
65
|
+
return { ok: false, detail: describePredicateExpectFailure(asV1.error) };
|
|
66
|
+
}
|
|
67
|
+
return { ok: true, value: flattened };
|
|
58
68
|
}
|
|
59
69
|
function describePredicateExpectFailure(error) {
|
|
70
|
+
const refused = error.issues.find((one) => 'custom' === one.code);
|
|
71
|
+
if (refused !== undefined)
|
|
72
|
+
return `${FlowParseNote.UNSUPPORTED_SHAPE}: ${refused.message}`;
|
|
73
|
+
// An unrecognized key FIRST: a strict object reports it on the object's own path with the names
|
|
74
|
+
// in `keys`, so reading `path[0]` misses the one thing worth saying. A misspelling is the failure
|
|
75
|
+
// this whole check exists to catch, and naming it is the difference between a fix and a hunt.
|
|
76
|
+
const unknownKey = error.issues.find((one) => 'unrecognized_keys' === one.code);
|
|
77
|
+
if (unknownKey !== undefined && 'unrecognized_keys' === unknownKey.code) {
|
|
78
|
+
const named = unknownKey.keys.map((k) => `"${k}"`).join(', ');
|
|
79
|
+
return `${FlowParseNote.UNSUPPORTED_SHAPE} key ${named}`;
|
|
80
|
+
}
|
|
60
81
|
const issue = error.issues[0];
|
|
61
82
|
if (undefined === issue)
|
|
62
83
|
return FlowParseNote.UNSUPPORTED_SHAPE;
|
|
@@ -102,8 +123,48 @@ export function coerceFlowFileExpects(raw) {
|
|
|
102
123
|
}
|
|
103
124
|
return { ok: true, value: next };
|
|
104
125
|
}
|
|
126
|
+
/**
|
|
127
|
+
* The issues a union rejection buries.
|
|
128
|
+
*
|
|
129
|
+
* `expect` accepts a predicate OR a v1 expect, and zod reports a union failure as one
|
|
130
|
+
* `invalid_union` issue whose branch errors live inside `unionErrors`. The one that matters is the
|
|
131
|
+
* v1 branch's `unrecognized_keys` — the misspelled field. Without this the file was still correctly
|
|
132
|
+
* REFUSED and the message said only "malformed", which sends the author back to hunt a
|
|
133
|
+
* one-character typo we had already identified.
|
|
134
|
+
*/
|
|
135
|
+
function flatten(issues) {
|
|
136
|
+
return issues.flatMap((issue) => 'invalid_union' === issue.code
|
|
137
|
+
? [issue, ...flatten(issue.unionErrors.flatMap((e) => e.issues))]
|
|
138
|
+
: [issue]);
|
|
139
|
+
}
|
|
105
140
|
export function describeFlowZodFailure(error) {
|
|
106
|
-
const
|
|
141
|
+
const all = flatten(error.issues);
|
|
142
|
+
/*
|
|
143
|
+
* The most SPECIFIC complaint wins, wherever in the union it was reported.
|
|
144
|
+
*
|
|
145
|
+
* An unrecognized key names the typo. Failing that, an issue whose path reaches INTO an expect
|
|
146
|
+
* names the field that was the wrong type — `{ signal: { count: 1 } }`, a signal object with no
|
|
147
|
+
* name, is reported on `steps.0.expect.signal` and that path is the whole answer. The union's own
|
|
148
|
+
* top-level `invalid_union` says only "malformed", which is true and useless.
|
|
149
|
+
*/
|
|
150
|
+
const named = (one) => {
|
|
151
|
+
// NOT the discriminator complaint. `invalid_union_discriminator` on `expect.kind` is the
|
|
152
|
+
// PREDICATE branch objecting that a v1 expect has no `kind` — which is true of every v1 expect
|
|
153
|
+
// ever written and tells the author nothing. The useful issue is the one from the branch they
|
|
154
|
+
// were actually writing.
|
|
155
|
+
if ('invalid_union_discriminator' === one.code)
|
|
156
|
+
return false;
|
|
157
|
+
return (('steps' === one.path[0] && 'expect' === one.path[2] && one.path.length > 3) ||
|
|
158
|
+
('success' === one.path[0] && one.path.length > 1));
|
|
159
|
+
};
|
|
160
|
+
// A refinement carries its own reason (a session ref, named by field), and that reason IS the fix.
|
|
161
|
+
const refused = all.find((one) => 'custom' === one.code);
|
|
162
|
+
if (refused !== undefined && 'steps' === refused.path[0] && 'number' === typeof refused.path[1]) {
|
|
163
|
+
return `${FlowParseNote.UNSUPPORTED_SHAPE} at step ${String(refused.path[1])}: ${refused.message}`;
|
|
164
|
+
}
|
|
165
|
+
if (refused !== undefined)
|
|
166
|
+
return `${FlowParseNote.UNSUPPORTED_SHAPE}: ${refused.message}`;
|
|
167
|
+
const issue = all.find((one) => 'unrecognized_keys' === one.code) ?? all.find(named) ?? error.issues[0];
|
|
107
168
|
if (undefined === issue)
|
|
108
169
|
return FlowParseNote.MALFORMED;
|
|
109
170
|
const path = issue.path;
|
|
@@ -139,6 +200,26 @@ export function parseFlowFileText(text) {
|
|
|
139
200
|
catch {
|
|
140
201
|
return { ok: false, code: FlowErrorCode.PARSE_FAILED, detail: FlowParseNote.NOT_JSON };
|
|
141
202
|
}
|
|
203
|
+
/*
|
|
204
|
+
* Version BEFORE schema. When the format does not match, nothing else in the file can be trusted
|
|
205
|
+
* to mean what this reader thinks it means, so the first unrelated field that happens to fail
|
|
206
|
+
* would be noise about the wrong thing -- and `version` is a `z.literal`, so the schema's own
|
|
207
|
+
* complaint about it is indistinguishable from a typo.
|
|
208
|
+
*
|
|
209
|
+
* Only a NUMBER that is not ours counts. A missing or non-numeric `version` is a damaged field
|
|
210
|
+
* rather than a different format, and stays PARSE_FAILED.
|
|
211
|
+
*/
|
|
212
|
+
const declared = isRecord(parsed) ? parsed['version'] : undefined;
|
|
213
|
+
if ('number' === typeof declared && !READABLE_FLOW_VERSIONS.has(declared)) {
|
|
214
|
+
return {
|
|
215
|
+
ok: false,
|
|
216
|
+
code: FlowErrorCode.WRONG_VERSION,
|
|
217
|
+
detail: `this flow file is version ${String(declared)} and this Reticle reads versions ` +
|
|
218
|
+
`${[...READABLE_FLOW_VERSIONS].join(', ')}. The file is not damaged, the reader is the ` +
|
|
219
|
+
'wrong one. ' +
|
|
220
|
+
'Upgrade or downgrade Reticle rather than editing the flow.',
|
|
221
|
+
};
|
|
222
|
+
}
|
|
142
223
|
const coerced = coerceFlowFileExpects(parsed);
|
|
143
224
|
if (!coerced.ok) {
|
|
144
225
|
return { ok: false, code: FlowErrorCode.PARSE_FAILED, detail: coerced.detail };
|
|
@@ -6,7 +6,7 @@ import { classifyFlowAssertions } from './flow-classify.js';
|
|
|
6
6
|
*
|
|
7
7
|
* A replay used to report only in the language of the DOM: step 3 drifted, this assertion failed.
|
|
8
8
|
* True, actionable, and illegible — the reader still had to reconstruct what stopped working. The
|
|
9
|
-
* ledger in `.reticle/intent
|
|
9
|
+
* ledger in `.reticle/intent/` already holds the other half (prose statements, declared → bound
|
|
10
10
|
* → proved, and the verdict that discharged each one), so a flow does not need its own notion of
|
|
11
11
|
* intent. It needs an id.
|
|
12
12
|
*
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { type FlowFile, type FlowStep, type Predicate } from '@reticlehq/core';
|
|
2
|
+
/** A short, stable identity for a journey's shape: its start page and its steps. */
|
|
3
|
+
export declare function journeyFingerprint(flow: Pick<FlowFile, 'startPath' | 'steps'>): string;
|
|
4
|
+
/**
|
|
5
|
+
* Fold two copies of one journey into one file.
|
|
6
|
+
*
|
|
7
|
+
* Gap-filling, never overwriting: whatever the kept flow already says — its name, its intent, a
|
|
8
|
+
* step's declared consequence, a quarantine a person wrote — survives, and the other copy only
|
|
9
|
+
* supplies what is missing. So a merge can add an assertion or an intent and can never remove one.
|
|
10
|
+
*/
|
|
11
|
+
export declare function mergeJourney(kept: FlowFile, other: FlowFile): FlowFile;
|
|
12
|
+
/**
|
|
13
|
+
* The name a new journey saves under.
|
|
14
|
+
*
|
|
15
|
+
* Its readable name when that is free or already this journey's; otherwise the same name with the
|
|
16
|
+
* fingerprint's head, so two different journeys that share an intent never overwrite each other.
|
|
17
|
+
*/
|
|
18
|
+
export declare function journeyName(base: string, fingerprint: string, taken: ReadonlyMap<string, string>): string;
|
|
19
|
+
/**
|
|
20
|
+
* What must hold before a journey and what it leaves behind, read from where it started and ended.
|
|
21
|
+
*
|
|
22
|
+
* Routes only: the page is the one piece of state every recorded step knows for certain, and a
|
|
23
|
+
* claim written from a guess about the rest would be a precondition nobody met. Written in the
|
|
24
|
+
* exact form `canFollow` compares by value, so A-then-B is answerable from the two files alone.
|
|
25
|
+
*/
|
|
26
|
+
export declare function routeClaims(startPath: string | undefined, steps: readonly FlowStep[]): {
|
|
27
|
+
requires?: Predicate[];
|
|
28
|
+
ensures?: Predicate[];
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* The preconditions only another flow can establish.
|
|
32
|
+
*
|
|
33
|
+
* A route claim on the page the flow starts on is not one of them: replay navigates there itself, so
|
|
34
|
+
* it neither opts the flow out of the reset nor gates the run. That is the claim a saved drive writes
|
|
35
|
+
* for the map (see routeClaims) — reading it as "starts from leftover state" would stop every
|
|
36
|
+
* auto-saved flow from ever being reset.
|
|
37
|
+
*/
|
|
38
|
+
export declare function establishedState(flow: FlowFile): readonly Predicate[];
|
|
39
|
+
/**
|
|
40
|
+
* Is the tab where the flow asked to start? Up to a trailing slash, and up to the query the flow
|
|
41
|
+
* did not ask about.
|
|
42
|
+
*
|
|
43
|
+
* `startPath` is the SPECIFICATION, so it decides what counts. A query it recorded is compared:
|
|
44
|
+
* `?tab=wrap` and `?tab=summary` are different pages, and a replay that starts on the wrong one
|
|
45
|
+
* proves nothing about the right one. A query it did NOT record is ignored: the tab carrying
|
|
46
|
+
* `?next=%2F` on a login page, or the identity params Reticle puts on a leased tab, are not the flow
|
|
47
|
+
* being elsewhere, and navigating to strip them costs a session for nothing.
|
|
48
|
+
*
|
|
49
|
+
* The asymmetry is the whole point and the reason `observed` and `expected` are named rather than
|
|
50
|
+
* `a` and `b`. Comparing with the query on both sides always re-navigated a query-bearing
|
|
51
|
+
* `startPath` (#1059, which killed the session mid-flow); comparing with it on neither side reads a
|
|
52
|
+
* tab on `?tab=summary` as already at `?tab=wrap`.
|
|
53
|
+
*/
|
|
54
|
+
export declare function samePath(observed: string, expected: string): boolean;
|