@reticlehq/server 2.4.0 → 2.6.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/SKILL.md +719 -0
- package/dist/bridge/auth-failure-reason.d.ts +19 -0
- package/dist/bridge/auth-failure-reason.js +29 -0
- package/dist/{bridge-security.d.ts → bridge/bridge-security.d.ts} +1 -1
- package/dist/{bridge-security.js → bridge/bridge-security.js} +1 -1
- package/dist/{bridge.d.ts → bridge/bridge.d.ts} +13 -1
- package/dist/{bridge.js → bridge/bridge.js} +95 -15
- package/dist/{bridge.test-harness.d.ts → bridge/bridge.test-harness.d.ts} +9 -11
- package/dist/{bridge.test-harness.js → bridge/bridge.test-harness.js} +18 -10
- package/dist/{pairing-token.js → bridge/pairing-token.js} +1 -1
- package/dist/capsule/capsule-store.d.ts +8 -3
- package/dist/capsule/capsule-store.js +7 -2
- package/dist/capsule/capsule.js +3 -3
- package/dist/capsule/causal-summary.d.ts +8 -0
- package/dist/capsule/causal-summary.js +22 -12
- package/dist/capsule/divergence.d.ts +9 -4
- package/dist/capsule/divergence.js +8 -8
- package/dist/capsule/predicate-to-links.js +11 -8
- package/dist/cli/cli-doctor.d.ts +1 -0
- package/dist/cli/cli-doctor.js +135 -0
- package/dist/{cli-flow-commands.d.ts → cli/cli-flow-commands.d.ts} +2 -2
- package/dist/{cli-flow-commands.js → cli/cli-flow-commands.js} +15 -15
- package/dist/{cli-launch.d.ts → cli/cli-launch.d.ts} +32 -3
- package/dist/cli/cli-launch.js +171 -0
- package/dist/cli/cli-parse-feedback.d.ts +31 -0
- package/dist/cli/cli-parse-feedback.js +56 -0
- package/dist/{cli-parse.d.ts → cli/cli-parse.d.ts} +6 -19
- package/dist/{cli-parse.js → cli/cli-parse.js} +118 -81
- package/dist/{cli-port.d.ts → cli/cli-port.d.ts} +12 -0
- package/dist/{cli-port.js → cli/cli-port.js} +9 -9
- package/dist/cli/cli-update-commands.d.ts +4 -0
- package/dist/cli/cli-update-commands.js +93 -0
- package/dist/{cli-verify.js → cli/cli-verify.js} +14 -14
- package/dist/{cloud-cli.js → cli/cloud-cli.js} +29 -24
- package/dist/cli/doctor-daemon-line.d.ts +37 -0
- package/dist/cli/doctor-daemon-line.js +35 -0
- package/dist/cli/port-holder.d.ts +43 -0
- package/dist/cli/port-holder.js +99 -0
- package/dist/cli.d.ts +2 -2
- package/dist/cli.js +302 -100
- package/dist/cloud/cloud-config.d.ts +0 -1
- package/dist/cloud/cloud-config.js +9 -9
- package/dist/cloud/cloud-sync.d.ts +1 -7
- package/dist/cloud/cloud-sync.js +12 -12
- package/dist/crawl/crawl-empty.d.ts +16 -0
- package/dist/crawl/crawl-empty.js +24 -0
- package/dist/crawl/crawl-tools.js +4 -1
- package/dist/crawl/crawl.js +26 -7
- package/dist/daemon/daemon-resilience.d.ts +73 -0
- package/dist/daemon/daemon-resilience.js +279 -0
- package/dist/daemon/daemon-usefulness.d.ts +61 -0
- package/dist/daemon/daemon-usefulness.js +62 -0
- package/dist/{daemon.d.ts → daemon/daemon.d.ts} +33 -8
- package/dist/{daemon.js → daemon/daemon.js} +105 -48
- package/dist/daemon/heartbeat.d.ts +115 -0
- package/dist/daemon/heartbeat.js +141 -0
- package/dist/daemon/idle-grace.d.ts +32 -0
- package/dist/daemon/idle-grace.js +41 -0
- package/dist/{idle-shutdown.d.ts → daemon/idle-shutdown.d.ts} +23 -2
- package/dist/{idle-shutdown.js → daemon/idle-shutdown.js} +37 -6
- package/dist/daemon/port-presence.d.ts +76 -0
- package/dist/daemon/port-presence.js +93 -0
- package/dist/daemon/state-dir.d.ts +7 -0
- package/dist/daemon/state-dir.js +59 -0
- package/dist/daemon/unclean-predecessor.d.ts +23 -0
- package/dist/daemon/unclean-predecessor.js +27 -0
- package/dist/domain/domain-model.js +2 -2
- package/dist/events/body-failures.js +8 -8
- package/dist/events/contradictions.d.ts +1 -1
- package/dist/events/contradictions.js +60 -10
- package/dist/events/echo-mismatch.js +10 -10
- package/dist/events/event-filters.js +6 -6
- package/dist/events/json-salvage.d.ts +2 -1
- package/dist/events/json-salvage.js +6 -6
- package/dist/events/observed-in-window.d.ts +12 -0
- package/dist/events/observed-in-window.js +21 -0
- package/dist/events/predicate-eval.d.ts +53 -20
- package/dist/events/predicate-eval.js +179 -55
- package/dist/events/predicate-parse.d.ts +23 -0
- package/dist/events/predicate-parse.js +54 -0
- package/dist/events/predicate.d.ts +7 -2
- package/dist/events/predicate.js +126 -35
- package/dist/events/reconcile.js +5 -5
- package/dist/events/ring-buffer.js +2 -17
- package/dist/events/stale-response.js +6 -6
- package/dist/events/testid-near-miss.d.ts +22 -0
- package/dist/events/testid-near-miss.js +33 -0
- package/dist/events/unit-mismatch.js +8 -8
- package/dist/flows/affected-tools.js +19 -4
- package/dist/flows/affected.js +1 -1
- package/dist/flows/annotate-target.d.ts +1 -0
- package/dist/flows/annotate-target.js +20 -0
- package/dist/flows/annotate-tools.js +25 -3
- package/dist/flows/annotate.js +17 -4
- package/dist/flows/assertion-integrity.d.ts +2 -1
- package/dist/flows/assertion-tiers-store.d.ts +3 -66
- package/dist/flows/assertion-tiers-store.js +2 -2
- package/dist/flows/attributed-failure.d.ts +17 -0
- package/dist/flows/attributed-failure.js +20 -0
- package/dist/flows/buddy-status.d.ts +2 -1
- package/dist/flows/buddy-status.js +2 -2
- package/dist/flows/coverage.d.ts +3 -2
- package/dist/flows/coverage.js +2 -2
- package/dist/flows/decision.js +24 -9
- package/dist/flows/empty-flow.d.ts +6 -0
- package/dist/flows/empty-flow.js +24 -0
- package/dist/flows/flake-store.d.ts +0 -1
- package/dist/flows/flake-store.js +1 -1
- package/dist/flows/flake.d.ts +2 -1
- package/dist/flows/flake.js +2 -2
- package/dist/flows/flow-classify.js +12 -0
- package/dist/flows/flow-replay-run.js +4 -4
- package/dist/flows/flow-replay.d.ts +6 -8
- package/dist/flows/flow-replay.js +132 -48
- package/dist/flows/flow-report.js +1 -1
- package/dist/flows/flow-scope.d.ts +1 -6
- package/dist/flows/flow-step-runners.d.ts +26 -6
- package/dist/flows/flow-step-runners.js +156 -6
- package/dist/flows/flow-success.js +20 -15
- package/dist/flows/flow-tools.js +63 -21
- package/dist/flows/flows.js +15 -11
- package/dist/flows/gate.d.ts +4 -3
- package/dist/flows/gate.js +1 -1
- package/dist/flows/heal-run.d.ts +0 -8
- package/dist/flows/heal-run.js +5 -5
- package/dist/flows/heal.js +3 -3
- package/dist/flows/predicate-to-expect.d.ts +38 -0
- package/dist/flows/predicate-to-expect.js +95 -0
- package/dist/flows/replay.d.ts +16 -1
- package/dist/flows/replay.js +58 -22
- package/dist/flows/role-anchor-nearest.d.ts +6 -0
- package/dist/flows/role-anchor-nearest.js +45 -0
- package/dist/flows/role-drift-reason.d.ts +20 -0
- package/dist/flows/role-drift-reason.js +39 -0
- package/dist/flows/server-verify.d.ts +1 -1
- package/dist/flows/server-verify.js +5 -5
- package/dist/flows/suite-flakes.d.ts +33 -0
- package/dist/flows/suite-flakes.js +32 -0
- package/dist/flows/verify-change-tools.js +32 -10
- package/dist/flows/verify-change-verdict.d.ts +15 -0
- package/dist/flows/verify-change-verdict.js +25 -0
- package/dist/flows/watch-batcher.d.ts +3 -2
- package/dist/honesty/already-true.d.ts +2 -0
- package/dist/honesty/already-true.js +31 -0
- package/dist/honesty/blind-spots.d.ts +35 -3
- package/dist/honesty/blind-spots.js +57 -12
- package/dist/honesty/honesty.d.ts +3 -2
- package/dist/honesty/honesty.js +4 -4
- package/dist/honesty/uncaptured-bodies.js +1 -1
- package/dist/honesty/unread-outcome.js +2 -2
- package/dist/honesty/verified.d.ts +31 -3
- package/dist/honesty/verified.js +64 -8
- package/dist/http-server.js +23 -7
- package/dist/hunt/hunt-report.d.ts +2 -1
- package/dist/hunt/hunt-report.js +2 -2
- package/dist/index.d.ts +9 -6
- package/dist/index.js +41 -25
- package/dist/init/agent-rules.d.ts +20 -1
- package/dist/init/agent-rules.js +64 -8
- package/dist/init/app-choice.d.ts +20 -0
- package/dist/init/app-choice.js +25 -0
- package/dist/init/astro-host.d.ts +21 -0
- package/dist/init/astro-host.js +27 -0
- package/dist/init/astro-patch.d.ts +16 -0
- package/dist/init/astro-patch.js +240 -0
- package/dist/init/capabilities.js +4 -1
- package/dist/init/cra.d.ts +50 -0
- package/dist/init/cra.js +106 -0
- package/dist/init/cursor.js +39 -4
- package/dist/init/desktop-doctor.d.ts +3 -2
- package/dist/init/desktop-doctor.js +4 -4
- package/dist/init/detect.d.ts +9 -2
- package/dist/init/detect.js +24 -5
- package/dist/init/mcp-clients.d.ts +81 -0
- package/dist/init/mcp-clients.js +248 -0
- package/dist/init/mcp-registered.d.ts +12 -0
- package/dist/init/mcp-registered.js +14 -0
- package/dist/init/mcp.d.ts +27 -2
- package/dist/init/mcp.js +40 -4
- package/dist/init/next-patch.d.ts +2 -4
- package/dist/init/next-patch.js +109 -19
- package/dist/init/node-io.js +29 -6
- package/dist/init/plan-framework.d.ts +16 -1
- package/dist/init/plan-framework.js +166 -8
- package/dist/init/plan.d.ts +85 -6
- package/dist/init/plan.js +196 -33
- package/dist/init/project-id.d.ts +5 -9
- package/dist/init/project-id.js +10 -22
- package/dist/init/run.d.ts +23 -1
- package/dist/init/run.js +338 -47
- package/dist/init/slash-command.d.ts +9 -0
- package/dist/init/slash-command.js +10 -1
- package/dist/init/snippets.d.ts +7 -10
- package/dist/init/snippets.js +44 -15
- package/dist/init/vite-config.js +1 -1
- package/dist/init/windows-quote.d.ts +38 -0
- package/dist/init/windows-quote.js +70 -0
- package/dist/init/workspace-apps.d.ts +23 -0
- package/dist/init/workspace-apps.js +68 -0
- package/dist/input/driven-redaction.js +1 -1
- package/dist/input/network-detail.js +8 -8
- package/dist/input/network-mock-tools.js +2 -2
- package/dist/input/network-mock.js +4 -4
- package/dist/input/real-input.js +8 -3
- package/dist/input/scroll-find.js +2 -2
- package/dist/input/scroll-tools.d.ts +0 -5
- package/dist/input/scroll-tools.js +11 -4
- package/dist/input/viewport-tools.js +2 -2
- package/dist/journal/ambient-store.d.ts +0 -2
- package/dist/journal/ambient-store.js +1 -1
- package/dist/journal/ambient.js +1 -1
- package/dist/journal/attach-journal.d.ts +2 -1
- package/dist/journal/deviation-report.js +6 -6
- package/dist/journal/deviation-service.js +1 -1
- package/dist/journal/envelope-store.d.ts +0 -2
- package/dist/journal/envelope-store.js +1 -1
- package/dist/journal/envelope.d.ts +2 -3
- package/dist/journal/envelope.js +3 -3
- package/dist/journal/journal-query.js +1 -1
- package/dist/journal/journal-recorder.d.ts +2 -1
- package/dist/journal/journal-recorder.js +2 -2
- package/dist/journal/rollups.js +9 -9
- package/dist/journal/session-end.d.ts +2 -1
- package/dist/journal/session-journal.js +4 -4
- package/dist/license/license.d.ts +0 -14
- package/dist/license/license.js +8 -8
- package/dist/log.d.ts +10 -0
- package/dist/log.js +11 -1
- package/dist/mcp/mcp-is-error.d.ts +13 -0
- package/dist/mcp/mcp-is-error.js +18 -0
- package/dist/mcp/mcp-outage.d.ts +32 -0
- package/dist/mcp/mcp-outage.js +55 -0
- package/dist/mcp/mcp-proxy.d.ts +134 -0
- package/dist/mcp/mcp-proxy.js +720 -0
- package/dist/{mcp.d.ts → mcp/mcp.d.ts} +13 -10
- package/dist/{mcp.js → mcp/mcp.js} +139 -37
- package/dist/mcp/proxy-handshake.d.ts +3 -0
- package/dist/mcp/proxy-handshake.js +54 -0
- package/dist/mcp/proxy-lifecycle.d.ts +64 -0
- package/dist/mcp/proxy-lifecycle.js +70 -0
- package/dist/mcp/tool-catalog-cache.d.ts +29 -0
- package/dist/mcp/tool-catalog-cache.js +55 -0
- package/dist/oracles/propose-consequences.d.ts +2 -1
- package/dist/oracles/propose-consequences.js +16 -11
- package/dist/oracles/self-instrument.d.ts +2 -1
- package/dist/oracles/self-instrument.js +1 -1
- package/dist/phenomena/phenomena.js +3 -3
- package/dist/platform.d.ts +16 -0
- package/dist/platform.js +16 -0
- package/dist/pool/browser-pool.d.ts +9 -0
- package/dist/pool/browser-pool.js +38 -9
- package/dist/pool/playwright-launcher.d.ts +15 -2
- package/dist/pool/playwright-launcher.js +21 -2
- package/dist/project/fs-port.js +1 -1
- package/dist/project/project-tools.js +2 -2
- package/dist/runs/build-verification-run.js +1 -1
- package/dist/runs/repair-prompt.d.ts +0 -6
- package/dist/runs/repair-prompt.js +3 -3
- package/dist/runs/risk-classify.js +1 -1
- package/dist/runs/run-diff.d.ts +0 -2
- package/dist/runs/run-diff.js +3 -3
- package/dist/runs/run-tools.js +2 -2
- package/dist/runs/verification-sync.js +2 -2
- package/dist/runs/verify-http.js +1 -1
- package/dist/runs/verify-server.js +1 -1
- package/dist/session/attachment-history.d.ts +46 -0
- package/dist/session/attachment-history.js +65 -0
- package/dist/session/command-timeout.d.ts +11 -3
- package/dist/session/command-timeout.js +2 -2
- package/dist/session/control-envelope.d.ts +6 -0
- package/dist/session/control-envelope.js +7 -1
- package/dist/session/dev-server-probe.d.ts +13 -0
- package/dist/session/dev-server-probe.js +52 -0
- package/dist/session/last-act.d.ts +17 -3
- package/dist/session/last-act.js +16 -4
- package/dist/session/live-control-tools.js +7 -2
- package/dist/session/live-control.js +1 -1
- package/dist/session/looks-like-dev-server.d.ts +14 -0
- package/dist/session/looks-like-dev-server.js +25 -0
- package/dist/session/no-session-diagnosis.d.ts +45 -0
- package/dist/session/no-session-diagnosis.js +120 -0
- package/dist/session/no-session-watch.d.ts +21 -0
- package/dist/session/no-session-watch.js +69 -0
- package/dist/session/observed-state.d.ts +4 -0
- package/dist/session/observed-state.js +19 -1
- package/dist/session/output-budget.js +1 -1
- package/dist/session/pending-commands.js +1 -0
- package/dist/session/session-health.d.ts +8 -1
- package/dist/session/session-health.js +8 -7
- package/dist/session/session-info.d.ts +43 -8
- package/dist/session/session-info.js +46 -1
- package/dist/session/session-lease.d.ts +0 -11
- package/dist/session/session-lease.js +1 -1
- package/dist/session/session-manager.d.ts +6 -0
- package/dist/session/session-manager.js +124 -12
- package/dist/session/session-readiness.d.ts +1 -1
- package/dist/session/session-readiness.js +6 -1
- package/dist/session/session-reaper.js +1 -1
- package/dist/session/session-reconnect.d.ts +32 -0
- package/dist/session/session-reconnect.js +42 -0
- package/dist/session/session.d.ts +15 -2
- package/dist/session/session.js +77 -26
- package/dist/session/state-select.d.ts +1 -1
- package/dist/session/state-select.js +1 -1
- package/dist/status-payload.d.ts +19 -0
- package/dist/status-payload.js +11 -0
- package/dist/telemetry/argument-shape.js +10 -1
- package/dist/telemetry/browser-mode.d.ts +27 -0
- package/dist/telemetry/browser-mode.js +33 -0
- package/dist/telemetry/bug-found.d.ts +17 -21
- package/dist/telemetry/bug-found.js +120 -8
- package/dist/telemetry/cli-telemetry.d.ts +2 -12
- package/dist/telemetry/cli-telemetry.js +27 -4
- package/dist/telemetry/crash-cause.d.ts +47 -0
- package/dist/telemetry/crash-cause.js +104 -0
- package/dist/telemetry/daemon-telemetry.d.ts +38 -1
- package/dist/telemetry/daemon-telemetry.js +37 -12
- package/dist/telemetry/dev-repo.js +4 -4
- package/dist/telemetry/error-class.d.ts +26 -0
- package/dist/telemetry/error-class.js +52 -0
- package/dist/telemetry/error-fingerprint.js +21 -2
- package/dist/telemetry/feedback-cli.d.ts +8 -3
- package/dist/telemetry/feedback-cli.js +52 -8
- package/dist/telemetry/feedback-context.d.ts +23 -6
- package/dist/telemetry/feedback-context.js +105 -5
- package/dist/telemetry/feedback-delivery.d.ts +20 -0
- package/dist/telemetry/feedback-delivery.js +30 -0
- package/dist/telemetry/feedback-outbox.d.ts +22 -0
- package/dist/telemetry/feedback-outbox.js +87 -0
- package/dist/telemetry/feedback.d.ts +22 -1
- package/dist/telemetry/feedback.js +67 -8
- package/dist/telemetry/git-facts.d.ts +7 -0
- package/dist/telemetry/git-facts.js +5 -5
- package/dist/telemetry/project-profile.js +2 -1
- package/dist/telemetry/session-metrics.d.ts +47 -3
- package/dist/telemetry/session-metrics.js +315 -15
- package/dist/telemetry/telemetry.d.ts +45 -2
- package/dist/telemetry/telemetry.js +150 -37
- package/dist/telemetry/verification-of.d.ts +22 -0
- package/dist/telemetry/verification-of.js +84 -0
- package/dist/tools/act-capsule.js +2 -2
- package/dist/tools/act-danger.d.ts +12 -0
- package/dist/tools/act-danger.js +20 -3
- package/dist/tools/act-helpers.js +4 -4
- package/dist/tools/act-tools.d.ts +0 -8
- package/dist/tools/act-tools.js +114 -118
- package/dist/tools/act-view.js +7 -7
- package/dist/tools/alias-args.d.ts +17 -0
- package/dist/tools/alias-args.js +25 -0
- package/dist/tools/assert-grade.js +9 -9
- package/dist/tools/assert-verdict.d.ts +9 -1
- package/dist/tools/assert-verdict.js +21 -6
- package/dist/tools/browser-tools.js +36 -11
- package/dist/tools/contract-tools.d.ts +0 -5
- package/dist/tools/contract-tools.js +27 -1
- package/dist/tools/coverage-identity.d.ts +34 -0
- package/dist/tools/coverage-identity.js +46 -0
- package/dist/tools/coverage-tools.js +10 -14
- package/dist/tools/dynamic-tools.d.ts +2 -1
- package/dist/tools/dynamic-tools.js +96 -19
- package/dist/tools/error-recovery.d.ts +34 -2
- package/dist/tools/error-recovery.js +255 -4
- package/dist/tools/feedback-invite.d.ts +48 -0
- package/dist/tools/feedback-invite.js +71 -0
- package/dist/tools/feedback-tools.d.ts +12 -1
- package/dist/tools/feedback-tools.js +28 -5
- package/dist/tools/invoke-tool.d.ts +0 -12
- package/dist/tools/invoke-tool.js +126 -33
- package/dist/tools/lease-hint.d.ts +17 -0
- package/dist/tools/lease-hint.js +24 -0
- package/dist/tools/lease-tools.js +6 -5
- package/dist/tools/merge-tools.d.ts +10 -1
- package/dist/tools/merge-tools.js +9 -3
- package/dist/tools/merged-name-redirect.d.ts +13 -0
- package/dist/tools/merged-name-redirect.js +55 -0
- package/dist/tools/navigate-arrival.d.ts +26 -0
- package/dist/tools/navigate-arrival.js +60 -0
- package/dist/tools/navigate-result.d.ts +31 -0
- package/dist/tools/navigate-result.js +38 -0
- package/dist/tools/observe-tools.js +72 -20
- package/dist/tools/observed-nothing.d.ts +25 -0
- package/dist/tools/observed-nothing.js +33 -0
- package/dist/tools/query-paginate.js +2 -2
- package/dist/tools/query-shape.d.ts +18 -0
- package/dist/tools/query-shape.js +39 -0
- package/dist/tools/read-tools.js +81 -20
- package/dist/tools/real-input-attempt.d.ts +28 -0
- package/dist/tools/real-input-attempt.js +93 -0
- package/dist/tools/reconcile-tools.js +7 -7
- package/dist/tools/reload-result.d.ts +14 -0
- package/dist/tools/reload-result.js +29 -0
- package/dist/tools/snapshot-delta.d.ts +4 -5
- package/dist/tools/snapshot-delta.js +0 -0
- package/dist/tools/tool-kit.d.ts +16 -0
- package/dist/tools/tool-surface.d.ts +83 -0
- package/dist/tools/tool-surface.js +217 -0
- package/dist/tools/tools-helpers.js +6 -6
- package/dist/tools/tools.d.ts +15 -0
- package/dist/tools/tools.js +53 -17
- package/dist/tools/unadvertised-help.d.ts +5 -0
- package/dist/tools/unadvertised-help.js +39 -0
- package/dist/trace.d.ts +69 -0
- package/dist/trace.js +150 -0
- package/dist/update/nudge-credit.d.ts +4 -0
- package/dist/update/nudge-credit.js +53 -0
- package/dist/update/sdk-sync.d.ts +21 -0
- package/dist/update/sdk-sync.js +42 -0
- package/dist/update/update-checker.js +3 -3
- package/dist/update/update-nudge.d.ts +63 -6
- package/dist/update/update-nudge.js +128 -15
- package/dist/update/updater.d.ts +1 -9
- package/dist/update/updater.js +13 -8
- package/dist/version/peer-announce.d.ts +4 -0
- package/dist/version/peer-announce.js +30 -0
- package/dist/{server-version.js → version/server-version.js} +5 -1
- package/dist/version/version-nudge.d.ts +28 -0
- package/dist/version/version-nudge.js +40 -0
- package/dist/version/version-skew.d.ts +66 -0
- package/dist/version/version-skew.js +71 -0
- package/dist/visual/visual-diff.js +2 -2
- package/dist/visual/visual-tools.js +4 -4
- package/docs/README.md +55 -0
- package/docs/agent-cheatsheet.md +85 -0
- package/docs/architecture.md +113 -0
- package/docs/benchmarks.md +255 -0
- package/docs/debugging.md +102 -0
- package/docs/deploy-checks.md +69 -0
- package/docs/desktop-apps.md +256 -0
- package/docs/enterprise.md +62 -0
- package/docs/fixtures-dispatch-receiver.yml +76 -0
- package/docs/fixtures.md +45 -0
- package/docs/flows.md +166 -0
- package/docs/gate-plan.md +98 -0
- package/docs/gates.md +117 -0
- package/docs/getting-started.md +469 -0
- package/docs/human-control.md +63 -0
- package/docs/integration-patterns.md +171 -0
- package/docs/local-registry.md +126 -0
- package/docs/matrix/2.5.0/claude-code-machine.json +19 -0
- package/docs/matrix/2.5.0/codex-machine.json +20 -0
- package/docs/matrix/2.5.0/cursor-machine.json +22 -0
- package/docs/matrix/2.5.0/gemini-machine.json +22 -0
- package/docs/matrix/2.5.0/opencode-machine.json +22 -0
- package/docs/matrix/2.5.0/vscode-machine.json +22 -0
- package/docs/matrix/2.5.0/windsurf-machine.json +22 -0
- package/docs/matrix/MATRIX.md +26 -0
- package/docs/matrix/README.md +59 -0
- package/docs/mint.json +61 -0
- package/docs/multi-agent-testing.md +55 -0
- package/docs/platform-integration.md +122 -0
- package/docs/system-map.md +152 -0
- package/docs/telemetry-contract.md +214 -0
- package/docs/telemetry.md +191 -0
- package/docs/testing.md +82 -0
- package/docs/token-efficiency.md +80 -0
- package/docs/usage.md +1046 -0
- package/package.json +8 -5
- package/dist/cli-doctor.d.ts +0 -6
- package/dist/cli-doctor.js +0 -60
- package/dist/cli-launch.js +0 -112
- package/dist/daemon-resilience.d.ts +0 -22
- package/dist/daemon-resilience.js +0 -85
- package/dist/mcp-proxy.d.ts +0 -67
- package/dist/mcp-proxy.js +0 -337
- package/dist/tools/profiles.d.ts +0 -37
- package/dist/tools/profiles.js +0 -139
- /package/dist/{pairing-token.d.ts → bridge/pairing-token.d.ts} +0 -0
- /package/dist/{token-auth.d.ts → bridge/token-auth.d.ts} +0 -0
- /package/dist/{token-auth.js → bridge/token-auth.js} +0 -0
- /package/dist/{cli-verify.d.ts → cli/cli-verify.d.ts} +0 -0
- /package/dist/{cloud-cli.d.ts → cli/cloud-cli.d.ts} +0 -0
- /package/dist/{server-version.d.ts → version/server-version.d.ts} +0 -0
package/dist/flows/replay.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { DANGEROUS_ACTION_CONFIRM_ARG, ReticleCommand, QueryBy, } from '@reticlehq/core';
|
|
2
2
|
import { ReticleTool } from '../tools/tool-names.js';
|
|
3
|
+
import { predicateToExpect, enforcedOnReplay } from './predicate-to-expect.js';
|
|
4
|
+
import { PredicateSchema } from '../events/predicate.js';
|
|
3
5
|
import { asString, asRecord } from '../tools/tools-helpers.js';
|
|
4
6
|
/**
|
|
5
7
|
* The note attached when a testid resolves to multiple live elements (the first match is used).
|
|
@@ -35,10 +37,38 @@ function sourceFromResult(res) {
|
|
|
35
37
|
if (typeof source['file'] !== 'string' || typeof source['line'] !== 'number')
|
|
36
38
|
return undefined;
|
|
37
39
|
const out = { file: source['file'], line: source['line'] };
|
|
38
|
-
if (typeof source['column']
|
|
40
|
+
if ('number' === typeof source['column'])
|
|
39
41
|
out['column'] = source['column'];
|
|
40
42
|
return out;
|
|
41
43
|
}
|
|
44
|
+
/**
|
|
45
|
+
* Capture an act into every in-flight recording, or do nothing when none is running.
|
|
46
|
+
*
|
|
47
|
+
* Every tool that drives the page must call this. reticle_act_and_wait did not, which is the tool
|
|
48
|
+
* the documented agent recipe drives with — so "record start -> drive -> record stop -> flow_save"
|
|
49
|
+
* saved a flow with zero steps.
|
|
50
|
+
*/
|
|
51
|
+
export function captureAct(recordings, args, res) {
|
|
52
|
+
if (0 === recordings.active().length)
|
|
53
|
+
return;
|
|
54
|
+
const step = compileActStep(args, res);
|
|
55
|
+
// Keep the assertion the agent actually made. `act_and_wait { until }` IS the agent saying what
|
|
56
|
+
// success means — 12 of 14 calls in a day carried one — and dropping it produced a flow graded
|
|
57
|
+
// "assertion-free: it will pass even if the feature is broken", which is the regression-suite
|
|
58
|
+
// story failing at its last step. Only kinds FlowExpect can express survive; see
|
|
59
|
+
// predicate-to-expect.ts.
|
|
60
|
+
const until = args['until'] ?? args['predicate'];
|
|
61
|
+
if (until !== undefined) {
|
|
62
|
+
const parsed = PredicateSchema.safeParse(until);
|
|
63
|
+
// Only what a replay actually CHECKS — recording an unenforced assertion would grade the flow
|
|
64
|
+
// "asserted" while nothing verifies it, which is a false green in the feature built to prevent
|
|
65
|
+
// them. See enforcedOnReplay.
|
|
66
|
+
const expect = parsed.success ? enforcedOnReplay(predicateToExpect(parsed.data)) : undefined;
|
|
67
|
+
if (expect !== undefined)
|
|
68
|
+
step.expect = expect;
|
|
69
|
+
}
|
|
70
|
+
recordings.capture(step);
|
|
71
|
+
}
|
|
42
72
|
/**
|
|
43
73
|
* Compile a single reticle_act invocation into a normalized RecordedStep using the action result. Anchor
|
|
44
74
|
* priority mirrors synthesizeAnchor: a data-testid is the gold standard; failing that, the element's
|
|
@@ -46,10 +76,20 @@ function sourceFromResult(res) {
|
|
|
46
76
|
* a volatile ref. Only when neither is available is the step ref-bound (in-session only).
|
|
47
77
|
*/
|
|
48
78
|
export function compileActStep(args, res) {
|
|
49
|
-
const
|
|
79
|
+
const { args: compiled, stable } = compileAnchorArgs(asRecord(res), asString(args['ref']) ?? '', asString(args['action']) ?? '', replayActionArgs(args['args']));
|
|
80
|
+
return { tool: ReticleTool.ACT, stable, args: compiled };
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* The ONE anchor chooser: testid (gold) > role+name (identifies an instance) > component/source
|
|
84
|
+
* (the JSX site) > a volatile ref (in-session only).
|
|
85
|
+
*
|
|
86
|
+
* It is shared because it was not: `compileSequenceStep` understood only a testid, so every
|
|
87
|
+
* act_sequence sub-step on a testid-less app compiled to a ref, saved as the degraded `unresolved`
|
|
88
|
+
* sentinel, and drifted on every replay — while a single act on the very same element compiled a
|
|
89
|
+
* perfectly stable anchor.
|
|
90
|
+
*/
|
|
91
|
+
function compileAnchorArgs(r, ref, action, actArgs) {
|
|
50
92
|
const testid = asString(r['testid']);
|
|
51
|
-
const action = asString(args['action']) ?? '';
|
|
52
|
-
const actArgs = replayActionArgs(args['args']);
|
|
53
93
|
const source = sourceFromResult(r);
|
|
54
94
|
if (testid !== undefined) {
|
|
55
95
|
// The testid anchors the step; source rides along purely so a failure can name a file. Carrying
|
|
@@ -62,7 +102,7 @@ export function compileActStep(args, res) {
|
|
|
62
102
|
};
|
|
63
103
|
if (source !== undefined)
|
|
64
104
|
testidArgs['source'] = source;
|
|
65
|
-
return {
|
|
105
|
+
return { args: testidArgs, stable: true };
|
|
66
106
|
}
|
|
67
107
|
// Role + NAME before component: a component/source anchor names the JSX site, so every row's copy
|
|
68
108
|
// of the same control collapses onto it. The accessible name separates them, and it is also the
|
|
@@ -80,7 +120,7 @@ export function compileActStep(args, res) {
|
|
|
80
120
|
};
|
|
81
121
|
if (source !== undefined)
|
|
82
122
|
roleArgs['source'] = source;
|
|
83
|
-
return {
|
|
123
|
+
return { args: roleArgs, stable: true };
|
|
84
124
|
}
|
|
85
125
|
const component = asString(r['component']);
|
|
86
126
|
if (component !== undefined || source !== undefined) {
|
|
@@ -89,15 +129,15 @@ export function compileActStep(args, res) {
|
|
|
89
129
|
componentArgs['component'] = component;
|
|
90
130
|
if (source !== undefined)
|
|
91
131
|
componentArgs['source'] = source;
|
|
92
|
-
return {
|
|
132
|
+
return { args: componentArgs, stable: true };
|
|
93
133
|
}
|
|
94
|
-
return {
|
|
95
|
-
tool: ReticleTool.ACT,
|
|
96
|
-
stable: false,
|
|
97
|
-
args: { ref: asString(args['ref']) ?? '', action, args: actArgs },
|
|
98
|
-
};
|
|
134
|
+
return { args: { ref, action, args: actArgs }, stable: false };
|
|
99
135
|
}
|
|
100
|
-
/**
|
|
136
|
+
/**
|
|
137
|
+
* Compile an reticle_act_sequence invocation, normalizing each sub-step through the SAME anchor
|
|
138
|
+
* priority a single act uses (see compileAnchorArgs) — not testid-or-nothing, which is what made
|
|
139
|
+
* every sequence unreplayable on an app without testids.
|
|
140
|
+
*/
|
|
101
141
|
export function compileSequenceStep(args, res) {
|
|
102
142
|
const inputSteps = Array.isArray(args['steps']) ? args['steps'] : [];
|
|
103
143
|
const resolved = Array.isArray(asRecord(res)['steps'])
|
|
@@ -106,14 +146,10 @@ export function compileSequenceStep(args, res) {
|
|
|
106
146
|
let stable = inputSteps.length > 0;
|
|
107
147
|
const subSteps = inputSteps.map((raw, i) => {
|
|
108
148
|
const step = asRecord(raw);
|
|
109
|
-
const
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
return { by: QueryBy.TESTID, value: testid, action, args: stepArgs };
|
|
114
|
-
}
|
|
115
|
-
stable = false;
|
|
116
|
-
return { ref: asString(step['ref']) ?? '', action, args: stepArgs };
|
|
149
|
+
const compiled = compileAnchorArgs(asRecord(resolved[i]), asString(step['ref']) ?? '', asString(step['action']) ?? '', replayActionArgs(step['args']));
|
|
150
|
+
if (!compiled.stable)
|
|
151
|
+
stable = false;
|
|
152
|
+
return compiled.args;
|
|
117
153
|
});
|
|
118
154
|
return { tool: ReticleTool.ACT_SEQUENCE, stable, args: { steps: subSteps } };
|
|
119
155
|
}
|
|
@@ -181,7 +217,7 @@ async function resolveRef(session, step) {
|
|
|
181
217
|
: { ref };
|
|
182
218
|
}
|
|
183
219
|
const ref = asString(step.ref);
|
|
184
|
-
if (ref === undefined || ref.length
|
|
220
|
+
if (ref === undefined || 0 === ref.length) {
|
|
185
221
|
throw new Error('step has no resolvable anchor (testid, role+name, component) and no ref');
|
|
186
222
|
}
|
|
187
223
|
return { ref, note: 'replayed by stale ref (not portable across sessions)' };
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The closest surviving control to a role+name anchor that no longer resolves.
|
|
3
|
+
*
|
|
4
|
+
* `runRoleStep` returned `nearest: null` as a literal, so `flow_heal` answered "no nearest match
|
|
5
|
+
* cleared the confidence floor" for every role-anchored drift — which reads as a judgement about
|
|
6
|
+
* candidates when in fact nothing had looked. Reported from a real sweep: a three-step flow failed
|
|
7
|
+
* on `button named 'Menu' did not resolve` and heal called it unhealable, however small the change.
|
|
8
|
+
*
|
|
9
|
+
* What this does NOT do is make role anchors auto-healable, and that restraint is the point. A
|
|
10
|
+
* testid is an identifier a developer put there deliberately; a role name is user-visible text where
|
|
11
|
+
* "Save" and "Save as" are one edit apart. Silently rebinding a step to a similar-looking control is
|
|
12
|
+
* precisely the wrong-element green this product exists to catch. So the bar here is deliberately
|
|
13
|
+
* higher than the testid path's: same role, unambiguous, and genuinely close — otherwise null, and
|
|
14
|
+
* the agent is told to add a testid.
|
|
15
|
+
*/
|
|
16
|
+
import { editDistance } from './flow-replay.js';
|
|
17
|
+
/**
|
|
18
|
+
* A name must be at least this similar to be worth showing: at most a third of it changed. A looser
|
|
19
|
+
* bar turns "no candidate" into "a confident wrong one", which is worse than saying nothing.
|
|
20
|
+
*/
|
|
21
|
+
const MAX_CHANGED_FRACTION = 1 / 3;
|
|
22
|
+
export function nearestRoleName(role, missing, present) {
|
|
23
|
+
const sameRole = present.filter((candidate) => candidate.role === role);
|
|
24
|
+
if (0 === sameRole.length)
|
|
25
|
+
return null;
|
|
26
|
+
const budget = Math.floor(missing.length * MAX_CHANGED_FRACTION);
|
|
27
|
+
let best = null;
|
|
28
|
+
let bestDistance = Number.POSITIVE_INFINITY;
|
|
29
|
+
let tied = false;
|
|
30
|
+
for (const candidate of sameRole) {
|
|
31
|
+
const distance = editDistance(missing, candidate.name);
|
|
32
|
+
if (distance < bestDistance) {
|
|
33
|
+
bestDistance = distance;
|
|
34
|
+
best = candidate.name;
|
|
35
|
+
tied = false;
|
|
36
|
+
}
|
|
37
|
+
else if (distance === bestDistance) {
|
|
38
|
+
tied = true;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
// A tie is a coin flip between two live controls; naming one would be inventing confidence.
|
|
42
|
+
if (null === best || tied || bestDistance > budget)
|
|
43
|
+
return null;
|
|
44
|
+
return best;
|
|
45
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What to say when a role+name anchor no longer resolves.
|
|
3
|
+
*
|
|
4
|
+
* The old message could contradict itself in the one case that matters most:
|
|
5
|
+
*
|
|
6
|
+
* role anchor button "Add User" not found; the closest surviving button is "Add User"
|
|
7
|
+
*
|
|
8
|
+
* Read literally that says the control both is and is not there, and it cost a reporter real
|
|
9
|
+
* debugging time. What it means is more useful than a rename: the stored name and the live
|
|
10
|
+
* accessible name are the SAME STRING, so the name is not what changed — the query matched nothing
|
|
11
|
+
* for some other reason.
|
|
12
|
+
*
|
|
13
|
+
* In the reported case the recorded name came from a `placeholder` — `reticle_query` reports
|
|
14
|
+
* `name:"Search User"` for `<input type="search" placeholder="Search User">` with no label — and the
|
|
15
|
+
* replay resolver computes accessible names differently, so the round trip cannot close. That is a
|
|
16
|
+
* real bug in its own right (two accessible-name implementations disagreeing), and while it is open
|
|
17
|
+
* the message is the only thing the reader has, because `flow_heal` correctly refuses to rebind a
|
|
18
|
+
* role anchor on its own.
|
|
19
|
+
*/
|
|
20
|
+
export declare function roleDriftReason(role: string, name: string, nearest: string | null): string;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What to say when a role+name anchor no longer resolves.
|
|
3
|
+
*
|
|
4
|
+
* The old message could contradict itself in the one case that matters most:
|
|
5
|
+
*
|
|
6
|
+
* role anchor button "Add User" not found; the closest surviving button is "Add User"
|
|
7
|
+
*
|
|
8
|
+
* Read literally that says the control both is and is not there, and it cost a reporter real
|
|
9
|
+
* debugging time. What it means is more useful than a rename: the stored name and the live
|
|
10
|
+
* accessible name are the SAME STRING, so the name is not what changed — the query matched nothing
|
|
11
|
+
* for some other reason.
|
|
12
|
+
*
|
|
13
|
+
* In the reported case the recorded name came from a `placeholder` — `reticle_query` reports
|
|
14
|
+
* `name:"Search User"` for `<input type="search" placeholder="Search User">` with no label — and the
|
|
15
|
+
* replay resolver computes accessible names differently, so the round trip cannot close. That is a
|
|
16
|
+
* real bug in its own right (two accessible-name implementations disagreeing), and while it is open
|
|
17
|
+
* the message is the only thing the reader has, because `flow_heal` correctly refuses to rebind a
|
|
18
|
+
* role anchor on its own.
|
|
19
|
+
*/
|
|
20
|
+
/** Same name to a reader: case and surrounding whitespace are not a rename. */
|
|
21
|
+
function sameName(a, b) {
|
|
22
|
+
return a.trim().toLowerCase() === b.trim().toLowerCase();
|
|
23
|
+
}
|
|
24
|
+
export function roleDriftReason(role, name, nearest) {
|
|
25
|
+
const label = `${role} "${name}"`;
|
|
26
|
+
if (null === nearest) {
|
|
27
|
+
return (`role anchor ${label} not found, and no surviving ${role} has a similar name — ` +
|
|
28
|
+
'anchor this step to a data-testid');
|
|
29
|
+
}
|
|
30
|
+
if (sameName(name, nearest)) {
|
|
31
|
+
// The one case the old wording made unreadable. Say what is ruled OUT — that is the information.
|
|
32
|
+
return (`role anchor ${label} did not resolve, but a ${role} with an identical name is on the page — ` +
|
|
33
|
+
'so this is NOT a rename and editing the label will not fix it. The recorded name and the ' +
|
|
34
|
+
'name the resolver computes disagree, which happens when the recorded one came from a ' +
|
|
35
|
+
'placeholder or title rather than a label or aria-label. Anchor this step to a data-testid.');
|
|
36
|
+
}
|
|
37
|
+
return (`role anchor ${label} not found; the closest surviving ${role} is "${nearest}" — ` +
|
|
38
|
+
'confirm it is the same control before rebinding, or add a data-testid');
|
|
39
|
+
}
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import type { SuiteVerdict } from '@reticlehq/core';
|
|
9
9
|
import type { ToolDeps } from '../tools/tools.js';
|
|
10
|
-
import { type ProjectCloud } from '
|
|
10
|
+
import { type ProjectCloud } from '../cloud/cloud-config.js';
|
|
11
11
|
import { type ServerVerification } from '../cloud/cloud-sync.js';
|
|
12
12
|
/** Map the hosted runner's report onto the local suite-verdict shape (status/total/passed/failed/failures). */
|
|
13
13
|
export declare function toSuiteVerdict(report: ServerVerification): SuiteVerdict;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { VerifyMode } from '
|
|
1
|
+
import { VerifyMode } from '../cloud/cloud-config.js';
|
|
2
2
|
import { submitServerVerification } from '../cloud/cloud-sync.js';
|
|
3
3
|
const PASS = 'pass';
|
|
4
4
|
const UNVERIFIED = 'unverified';
|
|
@@ -14,12 +14,12 @@ export function toSuiteVerdict(report) {
|
|
|
14
14
|
}));
|
|
15
15
|
const total = report.flows.length;
|
|
16
16
|
const passed = total - failures.length;
|
|
17
|
-
const status = report.verdict === PASS && failures.length
|
|
17
|
+
const status = report.verdict === PASS && 0 === failures.length ? 'pass' : 'fail';
|
|
18
18
|
return { status, total, passed, failed: failures.length, summary: report.summary, failures };
|
|
19
19
|
}
|
|
20
20
|
/** Run the suite on the hosted runner, or null to signal the caller should replay locally. */
|
|
21
21
|
export async function runServerVerify(deps, cloud, sessionId, flows) {
|
|
22
|
-
if (cloud.verify !== VerifyMode.SERVER || cloud.config
|
|
22
|
+
if (cloud.verify !== VerifyMode.SERVER || null === cloud.config)
|
|
23
23
|
return null;
|
|
24
24
|
let previewUrl;
|
|
25
25
|
try {
|
|
@@ -29,10 +29,10 @@ export async function runServerVerify(deps, cloud, sessionId, flows) {
|
|
|
29
29
|
previewUrl = undefined;
|
|
30
30
|
}
|
|
31
31
|
// The server hits the URL itself; with no URL (or a localhost one it can't reach) fall back to local.
|
|
32
|
-
if (previewUrl === undefined || previewUrl.length
|
|
32
|
+
if (previewUrl === undefined || 0 === previewUrl.length)
|
|
33
33
|
return null;
|
|
34
34
|
const report = await submitServerVerification({ previewUrl, flows, source: SOURCE }, cloud.config, (url, init) => fetch(url, init));
|
|
35
|
-
if (
|
|
35
|
+
if (null === report)
|
|
36
36
|
return null;
|
|
37
37
|
// The hosted runner said it couldn't actually verify (e.g. it's not enabled yet). Never surface that as
|
|
38
38
|
// a pass or a fail — fall back to the real local replay so the verdict reflects a browser that ran.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Accrue a suite run's outcomes into the flake ledger, and report what the ledger knows.
|
|
3
|
+
*
|
|
4
|
+
* This existed twice — once in `flow_verify`'s sequential branch and once, after #240, in its
|
|
5
|
+
* parallel branch. That duplication is not incidental: the parallel branch shipped WITHOUT it, so an
|
|
6
|
+
* agent verifying in parallel built no flake evidence at all and never saw the quarantine set, and
|
|
7
|
+
* nothing went red because the other copy was still correct. One function both branches call is what
|
|
8
|
+
* makes that miss impossible on one side only.
|
|
9
|
+
*
|
|
10
|
+
* It is also what makes the behaviour testable. The handler itself needs a live browser session, so
|
|
11
|
+
* both branches were only ever pinned by tests that RE-IMPLEMENTED this loop — which passed with the
|
|
12
|
+
* shipped code reverted (measured on #240: 4/4 green with the fix absent). Extracted, the real thing
|
|
13
|
+
* runs in a unit test.
|
|
14
|
+
*
|
|
15
|
+
* Best-effort on both halves, deliberately: a flake ledger is memory, not a gate. A full disk must
|
|
16
|
+
* never turn a working verification into an error.
|
|
17
|
+
*/
|
|
18
|
+
import { ReplayStatus } from '@reticlehq/core';
|
|
19
|
+
import type { FileSystemPort } from '../project/fs-port.js';
|
|
20
|
+
/** Just the shape this needs from a replay — so a test does not have to build a whole result. */
|
|
21
|
+
export interface FlakeOutcome {
|
|
22
|
+
readonly name: string;
|
|
23
|
+
readonly status: ReplayStatus;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Record every outcome, then ask which flows are intermittent.
|
|
27
|
+
*
|
|
28
|
+
* Anything that is not `OK` — `ERROR` or `DRIFT` — counts as a failure for the ledger. That is the
|
|
29
|
+
* rule both call sites already used, preserved rather than chosen here.
|
|
30
|
+
*/
|
|
31
|
+
export declare function recordSuiteFlakes(fs: FileSystemPort, reticleRoot: string, runs: readonly {
|
|
32
|
+
readonly replay: FlakeOutcome;
|
|
33
|
+
}[]): Promise<readonly string[]>;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Accrue a suite run's outcomes into the flake ledger, and report what the ledger knows.
|
|
3
|
+
*
|
|
4
|
+
* This existed twice — once in `flow_verify`'s sequential branch and once, after #240, in its
|
|
5
|
+
* parallel branch. That duplication is not incidental: the parallel branch shipped WITHOUT it, so an
|
|
6
|
+
* agent verifying in parallel built no flake evidence at all and never saw the quarantine set, and
|
|
7
|
+
* nothing went red because the other copy was still correct. One function both branches call is what
|
|
8
|
+
* makes that miss impossible on one side only.
|
|
9
|
+
*
|
|
10
|
+
* It is also what makes the behaviour testable. The handler itself needs a live browser session, so
|
|
11
|
+
* both branches were only ever pinned by tests that RE-IMPLEMENTED this loop — which passed with the
|
|
12
|
+
* shipped code reverted (measured on #240: 4/4 green with the fix absent). Extracted, the real thing
|
|
13
|
+
* runs in a unit test.
|
|
14
|
+
*
|
|
15
|
+
* Best-effort on both halves, deliberately: a flake ledger is memory, not a gate. A full disk must
|
|
16
|
+
* never turn a working verification into an error.
|
|
17
|
+
*/
|
|
18
|
+
import { ReplayStatus } from '@reticlehq/core';
|
|
19
|
+
import { FlakeStore } from './flake-store.js';
|
|
20
|
+
/**
|
|
21
|
+
* Record every outcome, then ask which flows are intermittent.
|
|
22
|
+
*
|
|
23
|
+
* Anything that is not `OK` — `ERROR` or `DRIFT` — counts as a failure for the ledger. That is the
|
|
24
|
+
* rule both call sites already used, preserved rather than chosen here.
|
|
25
|
+
*/
|
|
26
|
+
export async function recordSuiteFlakes(fs, reticleRoot, runs) {
|
|
27
|
+
const flakes = new FlakeStore(fs, reticleRoot);
|
|
28
|
+
for (const { replay } of runs) {
|
|
29
|
+
await flakes.record(replay.name, ReplayStatus.OK === replay.status).catch(() => undefined);
|
|
30
|
+
}
|
|
31
|
+
return flakes.flakyFlows().catch(() => []);
|
|
32
|
+
}
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
+
import { verdictForSuite } from './verify-change-verdict.js';
|
|
3
|
+
import { attributedFailures } from './attributed-failure.js';
|
|
2
4
|
import { Verified } from '@reticlehq/core';
|
|
3
5
|
import { ReticleTool } from '../tools/tool-names.js';
|
|
4
6
|
import { asNumber, asRecord, asString } from '../tools/tools-helpers.js';
|
|
5
|
-
import { loadNamedFlows, resolveChangedFiles } from '../cli-flow-commands.js';
|
|
7
|
+
import { loadNamedFlows, resolveChangedFiles } from '../cli/cli-flow-commands.js';
|
|
6
8
|
import { affectedSavedFlows } from './flow-sources.js';
|
|
7
9
|
import { FLOW_TOOLS } from './flow-tools.js';
|
|
8
10
|
import { findContradictions } from '../events/contradictions.js';
|
|
9
11
|
import { parseControls } from '../tools/coverage-tools.js';
|
|
10
12
|
import { ReticleCommand, SnapshotMode } from '@reticlehq/core';
|
|
11
|
-
const PASS = 'pass';
|
|
12
13
|
export const VERIFY_CHANGE_TOOLS = [
|
|
13
14
|
{
|
|
14
15
|
name: ReticleTool.VERIFY_CHANGE,
|
|
@@ -57,13 +58,13 @@ export const VERIFY_CHANGE_TOOLS = [
|
|
|
57
58
|
},
|
|
58
59
|
handler: async (deps, args) => {
|
|
59
60
|
const files = Array.isArray(args['files'])
|
|
60
|
-
? args['files'].filter((f) => typeof f
|
|
61
|
+
? args['files'].filter((f) => 'string' === typeof f)
|
|
61
62
|
: [];
|
|
62
63
|
const since = asString(args['since']);
|
|
63
64
|
const changedFiles = await resolveChangedFiles(files, since);
|
|
64
65
|
const flows = await loadNamedFlows(deps.fs, deps.reticleRoot);
|
|
65
66
|
const { affected, unknownProvenance } = affectedSavedFlows(flows, changedFiles);
|
|
66
|
-
if (changedFiles.length
|
|
67
|
+
if (0 === changedFiles.length) {
|
|
67
68
|
return {
|
|
68
69
|
verified: Verified.UNKNOWN,
|
|
69
70
|
because: 'no changed files were given, so there was nothing to decide about',
|
|
@@ -77,10 +78,10 @@ export const VERIFY_CHANGE_TOOLS = [
|
|
|
77
78
|
}
|
|
78
79
|
// The guard: an uncovered change is UNKNOWN. Reporting `yes` here would mean "your change is
|
|
79
80
|
// fine" on the strength of having run nothing at all.
|
|
80
|
-
if (affected.length
|
|
81
|
+
if (0 === affected.length) {
|
|
81
82
|
return {
|
|
82
83
|
verified: Verified.UNKNOWN,
|
|
83
|
-
because: `no saved flow covers ${changedFiles.length
|
|
84
|
+
because: `no saved flow covers ${1 === changedFiles.length ? 'this file' : 'these files'} — record one with reticle_record { action: "start" } then reticle_flow_save, or verify by driving the app directly`,
|
|
84
85
|
changedFiles,
|
|
85
86
|
flowsRun: [],
|
|
86
87
|
unknownProvenance,
|
|
@@ -104,11 +105,32 @@ export const VERIFY_CHANGE_TOOLS = [
|
|
|
104
105
|
}));
|
|
105
106
|
const failed = suite.failed ?? 0;
|
|
106
107
|
const passed = suite.passed ?? 0;
|
|
107
|
-
|
|
108
|
-
|
|
108
|
+
// `unverifiable` is NOT a failure — the suite ran and proved nothing, which is the same fact
|
|
109
|
+
// this tool answers UNKNOWN for an uncovered change. Treating it as a red emitted a bug_found
|
|
110
|
+
// that nothing earned. See verify-change-verdict.
|
|
111
|
+
const verdict = verdictForSuite(String(suite.status), failed);
|
|
112
|
+
// A NO must be EARNED by a failing flow genuinely tied to the changed files. `affectedSavedFlows`
|
|
113
|
+
// re-runs any flow whose sources it cannot determine — over-running beats skipping — and those
|
|
114
|
+
// land in `unknownProvenance`. Judging the change on them produced a negative verdict whose own
|
|
115
|
+
// explanation admitted the evidence was not tied to the file. See attributed-failure.
|
|
116
|
+
const failingNames = Array.isArray(suite.failures)
|
|
117
|
+
? suite.failures
|
|
118
|
+
.map((f) => 'object' === typeof f && f !== null ? asString(asRecord(f)['flow']) : undefined)
|
|
119
|
+
.filter((n) => n !== undefined)
|
|
120
|
+
: // No per-failure detail: the failure is somewhere in the set that ran, so ask the question
|
|
121
|
+
// of all of them rather than guessing which.
|
|
122
|
+
affected;
|
|
123
|
+
const attributed = attributedFailures(failingNames, unknownProvenance);
|
|
124
|
+
const earnedNo = verdict === Verified.NO && attributed.length > 0;
|
|
125
|
+
const reported = verdict === Verified.NO && !earnedNo ? Verified.UNKNOWN : verdict;
|
|
126
|
+
if (reported !== Verified.YES) {
|
|
109
127
|
return {
|
|
110
|
-
verified:
|
|
111
|
-
because:
|
|
128
|
+
verified: reported,
|
|
129
|
+
because: earnedNo
|
|
130
|
+
? `${String(failed)} of ${String(suite.total ?? affected.length)} covering flows failed, including ${attributed.join(', ')}, which cover the changed files${provenanceNote}`
|
|
131
|
+
: verdict === Verified.NO
|
|
132
|
+
? `flows failed, but Reticle cannot tell which sources any of them cover (${failingNames.join(', ')}) — so this says nothing about the files you changed. Re-record them so their sources are stamped, or read the replay yourself`
|
|
133
|
+
: `the covering flows ran but proved nothing (${String(suite.status)}) — they assert no observable consequence, so a green from them would not have meant your change is safe${provenanceNote}`,
|
|
112
134
|
changedFiles,
|
|
113
135
|
flowsRun: affected,
|
|
114
136
|
suite,
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a replayed suite's status entitles `verify_change` to claim.
|
|
3
|
+
*
|
|
4
|
+
* This was `suite.status !== 'pass'` meaning failure. That was true while the only statuses were
|
|
5
|
+
* pass and fail — but `flow_verify` now reports `unverifiable` for a suite that ran and proved
|
|
6
|
+
* nothing (an empty suite used to report `pass`, which was a false green). Under the old test,
|
|
7
|
+
* `unverifiable` fell into the failure branch: a suite that proved nothing came back as proof the
|
|
8
|
+
* change was broken, and emitted a `bug_found` for it.
|
|
9
|
+
*
|
|
10
|
+
* `unverifiable` is the same fact this tool already handles elsewhere as "no saved flow covers this
|
|
11
|
+
* change" — answered UNKNOWN, with the comment calling it "the honest answer, never a green: nothing
|
|
12
|
+
* ran, so nothing was proved". A red nothing earned is the mirror image of that green.
|
|
13
|
+
*/
|
|
14
|
+
import { Verified } from '@reticlehq/core';
|
|
15
|
+
export declare function verdictForSuite(status: string, failedCount: number): Verified;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a replayed suite's status entitles `verify_change` to claim.
|
|
3
|
+
*
|
|
4
|
+
* This was `suite.status !== 'pass'` meaning failure. That was true while the only statuses were
|
|
5
|
+
* pass and fail — but `flow_verify` now reports `unverifiable` for a suite that ran and proved
|
|
6
|
+
* nothing (an empty suite used to report `pass`, which was a false green). Under the old test,
|
|
7
|
+
* `unverifiable` fell into the failure branch: a suite that proved nothing came back as proof the
|
|
8
|
+
* change was broken, and emitted a `bug_found` for it.
|
|
9
|
+
*
|
|
10
|
+
* `unverifiable` is the same fact this tool already handles elsewhere as "no saved flow covers this
|
|
11
|
+
* change" — answered UNKNOWN, with the comment calling it "the honest answer, never a green: nothing
|
|
12
|
+
* ran, so nothing was proved". A red nothing earned is the mirror image of that green.
|
|
13
|
+
*/
|
|
14
|
+
import { Verified } from '@reticlehq/core';
|
|
15
|
+
const PASS = 'pass';
|
|
16
|
+
const FAIL = 'fail';
|
|
17
|
+
export function verdictForSuite(status, failedCount) {
|
|
18
|
+
// A real failure outranks everything: flows ran and some went red.
|
|
19
|
+
if (failedCount > 0 || status === FAIL)
|
|
20
|
+
return Verified.NO;
|
|
21
|
+
if (status === PASS)
|
|
22
|
+
return Verified.YES;
|
|
23
|
+
// `unverifiable`, or any status added later. Nothing was proved, so nothing is claimed.
|
|
24
|
+
return Verified.UNKNOWN;
|
|
25
|
+
}
|
|
@@ -4,10 +4,10 @@
|
|
|
4
4
|
* a quiet window, so we compute affected flows once per edit, not per keystroke. The scheduler (setTimeout)
|
|
5
5
|
* is injected, keeping the batching logic pure and unit-testable.
|
|
6
6
|
*/
|
|
7
|
-
|
|
7
|
+
interface WatchBatcher {
|
|
8
8
|
onChange(file: string): void;
|
|
9
9
|
}
|
|
10
|
-
|
|
10
|
+
interface WatchBatcherOptions {
|
|
11
11
|
debounceMs: number;
|
|
12
12
|
/** Injected scheduler (setTimeout) — deterministic in tests. */
|
|
13
13
|
schedule: (fn: () => void, ms: number) => void;
|
|
@@ -15,3 +15,4 @@ export interface WatchBatcherOptions {
|
|
|
15
15
|
onFlush: (files: string[]) => void;
|
|
16
16
|
}
|
|
17
17
|
export declare function createWatchBatcher(options: WatchBatcherOptions): WatchBatcher;
|
|
18
|
+
export {};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which predicates can be satisfied by something that was already true before the action.
|
|
3
|
+
*
|
|
4
|
+
* Event-based kinds (net, signal, route, console, animation, settled) are evaluated against the
|
|
5
|
+
* event buffer floored at the act's own cursor, so a stale event cannot satisfy them — that floor is
|
|
6
|
+
* why `act_and_wait` can trust them at all. `state` is read through the same floored path.
|
|
7
|
+
*
|
|
8
|
+
* `element` and `text` read the LIVE DOM, where no floor exists. A condition that held before the
|
|
9
|
+
* click holds after it and passes instantly, whatever the action did. Measured in the field: a click
|
|
10
|
+
* asserted with `{ kind: 'text', contains: 'Parallel Routes' }` returned `verified: "yes"` in 478ms
|
|
11
|
+
* against `routeChanges: 0`, because the predicate matched the nav link that was already on screen —
|
|
12
|
+
* the real navigation landed 1.8 seconds later.
|
|
13
|
+
*
|
|
14
|
+
* So these are the kinds worth evaluating BEFORE the act, to find out whether the green means
|
|
15
|
+
* anything.
|
|
16
|
+
*/
|
|
17
|
+
import { PredicateKind } from '@reticlehq/core';
|
|
18
|
+
export function readsDomState(predicate) {
|
|
19
|
+
switch (predicate.kind) {
|
|
20
|
+
case PredicateKind.ELEMENT:
|
|
21
|
+
case PredicateKind.TEXT:
|
|
22
|
+
return true;
|
|
23
|
+
case PredicateKind.ALL_OF:
|
|
24
|
+
case PredicateKind.ANY_OF:
|
|
25
|
+
return predicate.predicates.some(readsDomState);
|
|
26
|
+
case PredicateKind.NOT:
|
|
27
|
+
return readsDomState(predicate.predicate);
|
|
28
|
+
default:
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -6,12 +6,25 @@
|
|
|
6
6
|
*/
|
|
7
7
|
export { BlindSpotKind } from '@reticlehq/core';
|
|
8
8
|
import { BlindSpotKind, type ReticleEvent } from '@reticlehq/core';
|
|
9
|
-
|
|
9
|
+
interface BlindSpot {
|
|
10
10
|
kind: BlindSpotKind;
|
|
11
11
|
count: number;
|
|
12
12
|
}
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
/**
|
|
14
|
+
* Whether the observation window saw everything it was asked about.
|
|
15
|
+
*
|
|
16
|
+
* NOT core's VerdictStatus, which happens to spell one of its values 'partial' too: that one grades
|
|
17
|
+
* a verification RUN (some flows passed, some failed), this one grades what could be OBSERVED. Two
|
|
18
|
+
* meanings behind one string is exactly how a comparison ends up reading the wrong field, so they
|
|
19
|
+
* stay separate vocabularies.
|
|
20
|
+
*/
|
|
21
|
+
export declare const Coverage: {
|
|
22
|
+
readonly FULL: "full";
|
|
23
|
+
readonly PARTIAL: "partial";
|
|
24
|
+
};
|
|
25
|
+
export type Coverage = (typeof Coverage)[keyof typeof Coverage];
|
|
26
|
+
interface CoverageStatement {
|
|
27
|
+
coverage: Coverage;
|
|
15
28
|
/** Present only when partial — the human/agent-legible list of what went unobserved. */
|
|
16
29
|
note?: string;
|
|
17
30
|
spots: BlindSpot[];
|
|
@@ -52,3 +65,22 @@ export declare function blindSpotsFromEvents(events: readonly ReticleEvent[]): B
|
|
|
52
65
|
* from what happened to be said recently.
|
|
53
66
|
*/
|
|
54
67
|
export declare function blindSpotsFromState(state: Readonly<Record<string, number>>): BlindSpot[];
|
|
68
|
+
/**
|
|
69
|
+
* Events the BROWSER's own transport queue threw away inside this window, or 0.
|
|
70
|
+
*
|
|
71
|
+
* `RATE_LIMITED` — the BRIDGE sampling because events arrived faster than its per-second cap — is
|
|
72
|
+
* already the one blind spot that `impeachesCapture`, on the reasoning that a green over a window
|
|
73
|
+
* you did not fully see "would only describe what was observed". `TRANSPORT_OVERFLOW` is the exact
|
|
74
|
+
* same loss on the other side of the wire, and it was read in journal rollups and NOWHERE on the
|
|
75
|
+
* verdict path. So the identical condition downgraded a verdict from one side of the socket and was
|
|
76
|
+
* invisible from the other: a window that dropped 34 events graded `proved`, in a sentence whose own
|
|
77
|
+
* words were "over a clean capture".
|
|
78
|
+
*
|
|
79
|
+
* `TRUNCATED` is deliberately NOT counted. It names the channel it capped (a DOM-mutation flood on
|
|
80
|
+
* any busy page) and is routine churn; treating it as lost evidence would caveat every verdict on
|
|
81
|
+
* every real app, and a caveat that is always present is one nobody reads. TRANSPORT_OVERFLOW is the
|
|
82
|
+
* honest "arbitrary events are gone" marker — it cannot say which.
|
|
83
|
+
*/
|
|
84
|
+
export declare function droppedByTransport(events: readonly ReticleEvent[]): number;
|
|
85
|
+
/** The impeaching note for a transport gap, or undefined when the window was intact. */
|
|
86
|
+
export declare function transportGapNote(events: readonly ReticleEvent[]): string | undefined;
|