@reticlehq/server 2.3.0 → 2.5.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 +702 -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} +1 -1
- package/dist/{bridge.js → bridge/bridge.js} +105 -15
- package/dist/{bridge.test-harness.d.ts → bridge/bridge.test-harness.d.ts} +6 -11
- package/dist/{bridge.test-harness.js → bridge/bridge.test-harness.js} +21 -9
- 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-doctor.js → cli/cli-doctor.js} +9 -2
- 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.d.ts +143 -0
- package/dist/{cli-parse.js → cli/cli-parse.js} +146 -65
- package/dist/{cli-port.d.ts → cli/cli-port.d.ts} +15 -0
- package/dist/{cli-port.js → cli/cli-port.js} +29 -8
- 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.d.ts +2 -2
- package/dist/cli.js +182 -93
- 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 +49 -0
- package/dist/daemon/daemon-resilience.js +191 -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} +80 -48
- 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} +13 -0
- package/dist/{idle-shutdown.js → daemon/idle-shutdown.js} +27 -4
- 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 +43 -20
- package/dist/events/predicate-eval.js +159 -55
- package/dist/events/predicate.d.ts +2 -2
- package/dist/events/predicate.js +112 -35
- package/dist/events/reconcile.js +5 -5
- package/dist/events/ring-buffer.js +1 -1
- 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.d.ts +0 -6
- package/dist/flows/decision.js +64 -8
- 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 +5 -7
- package/dist/flows/flow-replay.js +114 -47
- 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 +157 -6
- package/dist/flows/flow-success.js +20 -15
- package/dist/flows/flow-tools.js +67 -13
- 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/server-verify.d.ts +1 -1
- package/dist/flows/server-verify.js +5 -5
- 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 +16 -3
- package/dist/honesty/blind-spots.js +24 -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 +22 -3
- package/dist/honesty/verified.js +48 -8
- package/dist/http-server.js +14 -7
- package/dist/hunt/hunt-report.d.ts +2 -1
- package/dist/hunt/hunt-report.js +2 -2
- package/dist/index.d.ts +8 -6
- package/dist/index.js +32 -25
- package/dist/init/agent-rules.d.ts +2 -1
- package/dist/init/agent-rules.js +45 -7
- 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.d.ts +17 -0
- package/dist/init/capabilities.js +54 -0
- package/dist/init/cra.d.ts +37 -0
- package/dist/init/cra.js +90 -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 +26 -0
- package/dist/init/detect.js +78 -5
- 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 +39 -0
- package/dist/init/next-patch.js +196 -0
- package/dist/init/node-io.js +48 -6
- package/dist/init/patch-kind.d.ts +20 -0
- package/dist/init/patch-kind.js +10 -0
- package/dist/init/plan-framework.d.ts +50 -0
- package/dist/init/plan-framework.js +341 -0
- package/dist/init/plan.d.ts +119 -6
- package/dist/init/plan.js +230 -131
- package/dist/init/project-id.d.ts +5 -9
- package/dist/init/project-id.js +10 -22
- package/dist/init/run.d.ts +22 -0
- package/dist/init/run.js +393 -28
- package/dist/init/slash-command.d.ts +27 -0
- package/dist/init/slash-command.js +61 -0
- package/dist/init/snippets.d.ts +41 -3
- package/dist/init/snippets.js +185 -10
- package/dist/init/vite-config.d.ts +4 -10
- package/dist/init/vite-config.js +15 -7
- package/dist/init/workspace-apps.d.ts +23 -0
- package/dist/init/workspace-apps.js +68 -0
- package/dist/input/driven-redaction.d.ts +15 -0
- package/dist/input/driven-redaction.js +76 -0
- package/dist/input/network-detail.d.ts +9 -3
- package/dist/input/network-detail.js +43 -23
- 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 +31 -0
- package/dist/mcp/mcp-outage.js +48 -0
- package/dist/mcp/mcp-proxy.d.ts +124 -0
- package/dist/mcp/mcp-proxy.js +697 -0
- package/dist/{mcp.d.ts → mcp/mcp.d.ts} +13 -10
- package/dist/{mcp.js → mcp/mcp.js} +110 -35
- 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/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/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 +38 -0
- package/dist/session/no-session-diagnosis.js +70 -0
- package/dist/session/no-session-watch.d.ts +19 -0
- package/dist/session/no-session-watch.js +61 -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/session-health.d.ts +8 -1
- package/dist/session/session-health.js +8 -7
- package/dist/session/session-info.d.ts +26 -7
- package/dist/session/session-info.js +42 -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 +3 -0
- package/dist/session/session-manager.js +58 -9
- 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 +20 -2
- package/dist/session/session.js +76 -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/daemon-telemetry.d.ts +19 -0
- package/dist/telemetry/daemon-telemetry.js +12 -2
- package/dist/telemetry/dev-repo.js +4 -4
- package/dist/telemetry/error-fingerprint.js +21 -2
- package/dist/telemetry/feedback-cli.d.ts +1 -1
- package/dist/telemetry/feedback-cli.js +4 -4
- package/dist/telemetry/feedback-context.d.ts +22 -0
- package/dist/telemetry/feedback-context.js +41 -1
- 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.js +1 -1
- package/dist/telemetry/session-metrics.d.ts +2 -0
- package/dist/telemetry/session-metrics.js +68 -6
- package/dist/telemetry/telemetry.d.ts +27 -2
- package/dist/telemetry/telemetry.js +119 -29
- 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 +84 -115
- 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 +3 -1
- package/dist/tools/assert-verdict.js +8 -5
- package/dist/tools/browser-tools.js +30 -11
- package/dist/tools/contract-tools.js +1 -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 +90 -18
- package/dist/tools/error-recovery.d.ts +20 -2
- package/dist/tools/error-recovery.js +179 -4
- 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 -6
- package/dist/tools/invoke-tool.js +57 -32
- 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-result.d.ts +17 -0
- package/dist/tools/navigate-result.js +31 -0
- package/dist/tools/observe-tools.js +54 -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 +85 -20
- package/dist/tools/real-input-attempt.d.ts +28 -0
- package/dist/tools/real-input-attempt.js +88 -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 +8 -0
- package/dist/tools/tool-surface.d.ts +83 -0
- package/dist/tools/tool-surface.js +204 -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/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.md +28 -0
- package/docs/flows.md +166 -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/mint.json +61 -0
- package/docs/multi-agent-testing.md +55 -0
- package/docs/platform-integration.md +122 -0
- package/docs/telemetry-contract.md +214 -0
- package/docs/telemetry.md +184 -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-launch.js +0 -112
- package/dist/cli-parse.d.ts +0 -140
- package/dist/daemon-resilience.d.ts +0 -22
- package/dist/daemon-resilience.js +0 -85
- package/dist/mcp-proxy.d.ts +0 -35
- package/dist/mcp-proxy.js +0 -190
- 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-doctor.d.ts → cli/cli-doctor.d.ts} +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
|
@@ -6,10 +6,11 @@
|
|
|
6
6
|
* point: the action window must open and close identically no matter which anchor found the element,
|
|
7
7
|
* or a step's events land in the wrong window depending on how it was addressed.
|
|
8
8
|
*/
|
|
9
|
-
import { AnchorKind, DriftReason, QueryBy, ReticleCommand, } from '@reticlehq/core';
|
|
9
|
+
import { AnchorKind, DEGRADED_ANCHOR_ROLE, DriftReason, QueryBy, ReticleCommand, } from '@reticlehq/core';
|
|
10
10
|
import { ReticleTool } from '../tools/tool-names.js';
|
|
11
11
|
import { replayActionArgs } from './replay.js';
|
|
12
|
-
import { componentLabel, componentQueryArgs, resolveQuery } from './flow-replay.js';
|
|
12
|
+
import { anchorLabel, componentLabel, componentQueryArgs, resolveQuery, testidDrift, } from './flow-replay.js';
|
|
13
|
+
import { nearestRoleName } from './role-anchor-nearest.js';
|
|
13
14
|
/** Query args for a NAMED role anchor — the handle that identifies an instance, not a JSX site. */
|
|
14
15
|
function roleQueryArgs(anchor) {
|
|
15
16
|
return {
|
|
@@ -19,11 +20,53 @@ function roleQueryArgs(anchor) {
|
|
|
19
20
|
};
|
|
20
21
|
}
|
|
21
22
|
/** Run one role+name-anchored step: re-resolve via QUERY by:'role', ACT on the live ref, else drift. */
|
|
23
|
+
/**
|
|
24
|
+
* The controls actually on the page, as role + name, for the nearest-match scan.
|
|
25
|
+
*
|
|
26
|
+
* Reads the interactive snapshot rather than a second query per candidate: one round trip, and it is
|
|
27
|
+
* the same view the coverage tool uses, so "what is on this page" has one definition.
|
|
28
|
+
*/
|
|
29
|
+
async function nearestRoleNameOnPage(session, anchor) {
|
|
30
|
+
const wanted = 'string' === typeof anchor.name ? anchor.name : undefined;
|
|
31
|
+
if (wanted === undefined)
|
|
32
|
+
return null;
|
|
33
|
+
try {
|
|
34
|
+
// Straight off the QUERY result: resolveQuery reduces to refs, and the NAMES are what this needs.
|
|
35
|
+
const result = await session.command(ReticleCommand.QUERY, {
|
|
36
|
+
by: QueryBy.ROLE,
|
|
37
|
+
value: anchor.role,
|
|
38
|
+
});
|
|
39
|
+
const payload = result.result;
|
|
40
|
+
const elements = 'object' === typeof payload && payload !== null
|
|
41
|
+
? payload.elements
|
|
42
|
+
: undefined;
|
|
43
|
+
if (!Array.isArray(elements))
|
|
44
|
+
return null;
|
|
45
|
+
const candidates = elements
|
|
46
|
+
.map((element) => 'object' === typeof element && element !== null
|
|
47
|
+
? element.name
|
|
48
|
+
: undefined)
|
|
49
|
+
.filter((name) => 'string' === typeof name && name.length > 0)
|
|
50
|
+
.map((name) => ({ role: anchor.role, name }));
|
|
51
|
+
return nearestRoleName(anchor.role, wanted, candidates);
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
// A drift report must never fail because the suggestion lookup did.
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
22
58
|
export async function runRoleStep(session, step, index, anchor, confirmDangerous, sleep) {
|
|
23
59
|
const label = `${anchor.role} "${String(anchor.name)}"`;
|
|
24
60
|
const { refs } = await resolveQuery(session, roleQueryArgs(anchor), sleep);
|
|
25
61
|
const ref = refs[0];
|
|
26
62
|
if (ref === undefined) {
|
|
63
|
+
// `nearest` used to be the literal null, so heal answered "no nearest match cleared the
|
|
64
|
+
// confidence floor" for EVERY role-anchored drift — a structural limit reported as a judgement
|
|
65
|
+
// about candidates, when nothing had looked. It looks now, and it is deliberately stricter than
|
|
66
|
+
// the testid path: a role name is user-visible text, so a near-match is far more likely to be a
|
|
67
|
+
// different control than a renamed one. A candidate here is INFORMATION for the agent; heal
|
|
68
|
+
// still refuses to rebind a role anchor on its own.
|
|
69
|
+
const nearest = await nearestRoleNameOnPage(session, anchor);
|
|
27
70
|
return {
|
|
28
71
|
step: index,
|
|
29
72
|
tool: step.tool,
|
|
@@ -31,9 +74,11 @@ export async function runRoleStep(session, step, index, anchor, confirmDangerous
|
|
|
31
74
|
ok: false,
|
|
32
75
|
drift: {
|
|
33
76
|
reasonKind: DriftReason.COMPONENT_NOT_FOUND,
|
|
34
|
-
reason:
|
|
77
|
+
reason: null === nearest
|
|
78
|
+
? `role anchor ${label} not found, and no surviving ${anchor.role} has a similar name — anchor this step to a data-testid`
|
|
79
|
+
: `role anchor ${label} not found; the closest surviving ${anchor.role} is "${nearest}" — confirm it is the same control before rebinding, or add a data-testid`,
|
|
35
80
|
anchor: label,
|
|
36
|
-
nearest
|
|
81
|
+
nearest,
|
|
37
82
|
},
|
|
38
83
|
};
|
|
39
84
|
}
|
|
@@ -43,7 +88,7 @@ export async function runRoleStep(session, step, index, anchor, confirmDangerous
|
|
|
43
88
|
* Dispatch one already-resolved step. Shared so every anchor kind runs the action the same way —
|
|
44
89
|
* including the action window, whose open/close must not depend on which anchor found the element.
|
|
45
90
|
*/
|
|
46
|
-
|
|
91
|
+
async function actOnResolvedRef(session, step, index, label, ref, confirmDangerous) {
|
|
47
92
|
session.beginAction?.(ReticleTool.FLOW_REPLAY, { ref, action: step.action ?? '' });
|
|
48
93
|
let act;
|
|
49
94
|
try {
|
|
@@ -65,7 +110,7 @@ export async function actOnResolvedRef(session, step, index, label, ref, confirm
|
|
|
65
110
|
export async function runComponentStep(session, step, index, anchor, confirmDangerous, sleep) {
|
|
66
111
|
const label = componentLabel(anchor);
|
|
67
112
|
const { refs } = await resolveQuery(session, componentQueryArgs(anchor), sleep);
|
|
68
|
-
if (refs.length
|
|
113
|
+
if (0 === refs.length) {
|
|
69
114
|
return {
|
|
70
115
|
step: index,
|
|
71
116
|
tool: step.tool,
|
|
@@ -100,3 +145,109 @@ export async function runComponentStep(session, step, index, anchor, confirmDang
|
|
|
100
145
|
result.error = act.error ?? 'command failed';
|
|
101
146
|
return result;
|
|
102
147
|
}
|
|
148
|
+
/**
|
|
149
|
+
* DEGRADED_ANCHOR_ROLE is a MARKER ("no anchor could be determined"), never a locator.
|
|
150
|
+
*
|
|
151
|
+
* Recognising it is most of the fix: a nameless-ROLE anchor used to fall through to the testid
|
|
152
|
+
* runner, which asked the DOM eight times for a testid literally named "unresolved", found none (it
|
|
153
|
+
* never exists), and reported a MISSING ELEMENT. It then ran edit-distance against the word
|
|
154
|
+
* "unresolved" and offered the nearest testid as a rebind target — which flow_verify printed while
|
|
155
|
+
* flow_heal refused it on confidence. Two tools contradicting each other over a candidate that never
|
|
156
|
+
* meant anything.
|
|
157
|
+
*
|
|
158
|
+
* Both recorders emit this sentinel, so the check belongs here on the replay side, where all of them
|
|
159
|
+
* land.
|
|
160
|
+
*/
|
|
161
|
+
export function isDegradedAnchor(anchor) {
|
|
162
|
+
return (anchor.kind === AnchorKind.ROLE &&
|
|
163
|
+
anchor.role === DEGRADED_ANCHOR_ROLE &&
|
|
164
|
+
anchor.name === undefined);
|
|
165
|
+
}
|
|
166
|
+
/** What a step with no resolvable anchor reports, instead of a missing-element story. */
|
|
167
|
+
const DEGRADED_STEP_REASON = 'recorded without a resolvable anchor (no data-testid, no accessible role+name), so it can never ' +
|
|
168
|
+
'resolve on replay — add a data-testid to the element and record this flow again';
|
|
169
|
+
export function degradedStepResult(step, index, label) {
|
|
170
|
+
return {
|
|
171
|
+
step: index,
|
|
172
|
+
tool: step.tool,
|
|
173
|
+
anchor: label,
|
|
174
|
+
ok: false,
|
|
175
|
+
drift: {
|
|
176
|
+
reasonKind: DriftReason.ANCHOR_DEGRADED,
|
|
177
|
+
reason: DEGRADED_STEP_REASON,
|
|
178
|
+
anchor: label,
|
|
179
|
+
// Deliberately null: the old path produced a nearest match to the WORD "unresolved", which is
|
|
180
|
+
// not a candidate for anything.
|
|
181
|
+
nearest: null,
|
|
182
|
+
},
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
/** QUERY args for an element anchor — null when the anchor addresses no element. */
|
|
186
|
+
function anchorQueryArgs(anchor) {
|
|
187
|
+
if (anchor.kind === AnchorKind.TESTID)
|
|
188
|
+
return { by: QueryBy.TESTID, value: anchor.value };
|
|
189
|
+
if (anchor.kind === AnchorKind.COMPONENT)
|
|
190
|
+
return componentQueryArgs(anchor);
|
|
191
|
+
if (anchor.kind === AnchorKind.ROLE && !isDegradedAnchor(anchor))
|
|
192
|
+
return roleQueryArgs(anchor);
|
|
193
|
+
return null;
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Run an act_sequence step: resolve every sub-step's OWN anchor, then dispatch the whole thing as one
|
|
197
|
+
* ACT_SEQUENCE — the same shape `replayProgram` already uses, so a recorded sequence and a saved one
|
|
198
|
+
* execute identically.
|
|
199
|
+
*
|
|
200
|
+
* `replayFlow` had no sequence branch at all. It dispatches on `anchor.kind`, so a saved sequence fell
|
|
201
|
+
* to the testid runner and ran ONE act with `action: ''` (a saved sequence carries no top-level
|
|
202
|
+
* action) — sub-steps 2..n never executed. Fixing the anchor without this would have turned a visible
|
|
203
|
+
* drift into a silent partial replay reporting ok.
|
|
204
|
+
*/
|
|
205
|
+
export async function runSequenceStep(session, step, index, subs, confirmDangerous, sleep) {
|
|
206
|
+
const live = [];
|
|
207
|
+
for (const [subIndex, sub] of subs.entries()) {
|
|
208
|
+
const label = `${anchorLabel(sub.anchor)} (sub-step ${String(subIndex)})`;
|
|
209
|
+
const queryArgs = anchorQueryArgs(sub.anchor);
|
|
210
|
+
if (null === queryArgs)
|
|
211
|
+
return degradedStepResult(step, index, label);
|
|
212
|
+
const { refs, hint } = await resolveQuery(session, queryArgs, sleep);
|
|
213
|
+
const ref = refs[0];
|
|
214
|
+
if (ref === undefined) {
|
|
215
|
+
return {
|
|
216
|
+
step: index,
|
|
217
|
+
tool: step.tool,
|
|
218
|
+
anchor: label,
|
|
219
|
+
ok: false,
|
|
220
|
+
drift: sub.anchor.kind === AnchorKind.TESTID
|
|
221
|
+
? testidDrift(sub.anchor.value, hint)
|
|
222
|
+
: {
|
|
223
|
+
reasonKind: DriftReason.COMPONENT_NOT_FOUND,
|
|
224
|
+
reason: `anchor ${label} not found`,
|
|
225
|
+
anchor: label,
|
|
226
|
+
nearest: null,
|
|
227
|
+
},
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
live.push({
|
|
231
|
+
ref,
|
|
232
|
+
action: sub.action ?? '',
|
|
233
|
+
args: replayActionArgs(sub.args, confirmDangerous),
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
session.beginAction?.(ReticleTool.FLOW_REPLAY, { steps: live.length });
|
|
237
|
+
let act;
|
|
238
|
+
try {
|
|
239
|
+
act = await session.command(ReticleCommand.ACT_SEQUENCE, { steps: live });
|
|
240
|
+
}
|
|
241
|
+
finally {
|
|
242
|
+
session.finishAction?.();
|
|
243
|
+
}
|
|
244
|
+
const result = {
|
|
245
|
+
step: index,
|
|
246
|
+
tool: step.tool,
|
|
247
|
+
anchor: anchorLabel(step.anchor),
|
|
248
|
+
ok: act.ok,
|
|
249
|
+
};
|
|
250
|
+
if (!act.ok)
|
|
251
|
+
result.error = act.error ?? 'command failed';
|
|
252
|
+
return result;
|
|
253
|
+
}
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* tool and the `@reticlehq/test` spec runner share one implementation — no divergent oracle.
|
|
13
13
|
* Pure: no IO, no clock.
|
|
14
14
|
*/
|
|
15
|
-
import { AnchorKind } from '@reticlehq/core';
|
|
15
|
+
import { AnchorKind, PredicateKind } from '@reticlehq/core';
|
|
16
16
|
/** The dynamic (LLM-output) testids whose presence is never asserted — same rule replay uses. */
|
|
17
17
|
export function dynamicTestids(flow) {
|
|
18
18
|
return new Set((flow.dynamic ?? [])
|
|
@@ -28,7 +28,7 @@ export function successLabel(success) {
|
|
|
28
28
|
if (success.net !== undefined)
|
|
29
29
|
return success.net.urlContains ?? success.net.method ?? 'net';
|
|
30
30
|
if (success.console !== undefined) {
|
|
31
|
-
return `console:${success.console.absent
|
|
31
|
+
return `console:${true === success.console.absent ? 'clean' : (success.console.level ?? 'error')}`;
|
|
32
32
|
}
|
|
33
33
|
if (success.state !== undefined)
|
|
34
34
|
return `state:${success.state.path}`;
|
|
@@ -39,11 +39,11 @@ export function successToPredicate(success, dynamic) {
|
|
|
39
39
|
const parts = [];
|
|
40
40
|
if (success.signal !== undefined) {
|
|
41
41
|
parts.push(success.signalData !== undefined
|
|
42
|
-
? { kind:
|
|
43
|
-
: { kind:
|
|
42
|
+
? { kind: PredicateKind.SIGNAL, name: success.signal, dataMatches: success.signalData }
|
|
43
|
+
: { kind: PredicateKind.SIGNAL, name: success.signal });
|
|
44
44
|
}
|
|
45
45
|
if (success.net !== undefined) {
|
|
46
|
-
const net = { kind:
|
|
46
|
+
const net = { kind: PredicateKind.NET };
|
|
47
47
|
if (success.net.method !== undefined)
|
|
48
48
|
net.method = success.net.method;
|
|
49
49
|
if (success.net.urlContains !== undefined)
|
|
@@ -56,12 +56,14 @@ export function successToPredicate(success, dynamic) {
|
|
|
56
56
|
// exact count (e.g. 1) is transiently satisfied the instant the FIRST matching request lands —
|
|
57
57
|
// before a double-submit's duplicate arrives. Gating on `settled` forces the count to be read
|
|
58
58
|
// only after the network has gone quiet, so the duplicate IS counted and the over-count fails.
|
|
59
|
-
parts.push({ kind:
|
|
59
|
+
parts.push({ kind: PredicateKind.SETTLED });
|
|
60
60
|
}
|
|
61
61
|
parts.push(net);
|
|
62
62
|
}
|
|
63
63
|
if (success.console !== undefined) {
|
|
64
|
-
const con = {
|
|
64
|
+
const con = {
|
|
65
|
+
kind: PredicateKind.CONSOLE,
|
|
66
|
+
};
|
|
65
67
|
if (success.console.level !== undefined)
|
|
66
68
|
con.level = success.console.level;
|
|
67
69
|
if (success.console.absent !== undefined) {
|
|
@@ -70,13 +72,16 @@ export function successToPredicate(success, dynamic) {
|
|
|
70
72
|
// error yet" at the first poll and passes BEFORE the action's error fires. Gate on `settled` so
|
|
71
73
|
// the console is read only after the page quiets, by which point any error is in the buffer.
|
|
72
74
|
if (success.console.absent)
|
|
73
|
-
parts.push({ kind:
|
|
75
|
+
parts.push({ kind: PredicateKind.SETTLED });
|
|
74
76
|
}
|
|
75
77
|
parts.push(con);
|
|
76
78
|
}
|
|
77
79
|
const state = success.state;
|
|
78
80
|
if (state !== undefined) {
|
|
79
|
-
const part = {
|
|
81
|
+
const part = {
|
|
82
|
+
kind: PredicateKind.STATE,
|
|
83
|
+
path: state.path,
|
|
84
|
+
};
|
|
80
85
|
if (state.store !== undefined)
|
|
81
86
|
part.store = state.store;
|
|
82
87
|
if (state.equals !== undefined)
|
|
@@ -85,8 +90,8 @@ export function successToPredicate(success, dynamic) {
|
|
|
85
90
|
// wait-for-change. A wait-until-true waiter would pass the instant the path already equals the
|
|
86
91
|
// value — which, for "an unrelated path stayed put", is true BEFORE a side-effect leak fires. Gate
|
|
87
92
|
// on `settled` so the read happens after the page quiets, by which point the leak has landed.
|
|
88
|
-
if (state.hold
|
|
89
|
-
parts.push({ kind:
|
|
93
|
+
if (true === state.hold)
|
|
94
|
+
parts.push({ kind: PredicateKind.SETTLED });
|
|
90
95
|
parts.push(part);
|
|
91
96
|
}
|
|
92
97
|
const element = success.element;
|
|
@@ -102,15 +107,15 @@ export function successToPredicate(success, dynamic) {
|
|
|
102
107
|
if (element.name !== undefined)
|
|
103
108
|
query['name'] = element.name;
|
|
104
109
|
if (Object.keys(query).length > 0)
|
|
105
|
-
parts.push({ kind:
|
|
110
|
+
parts.push({ kind: PredicateKind.ELEMENT, query });
|
|
106
111
|
}
|
|
107
112
|
}
|
|
108
113
|
const [first] = parts;
|
|
109
|
-
if (parts.length
|
|
114
|
+
if (0 === parts.length)
|
|
110
115
|
return undefined;
|
|
111
|
-
if (parts.length
|
|
116
|
+
if (1 === parts.length && first !== undefined)
|
|
112
117
|
return first;
|
|
113
|
-
return { kind:
|
|
118
|
+
return { kind: PredicateKind.ALL_OF, predicates: parts };
|
|
114
119
|
}
|
|
115
120
|
/**
|
|
116
121
|
* Assert a flow's success end-condition after replay. Delegates to the injected waitForSignal (the
|
package/dist/flows/flow-tools.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
+
import { emptyFlowRefusal } from './empty-flow.js';
|
|
3
|
+
import { aliasParam } from '../tools/alias-args.js';
|
|
2
4
|
import { FlowErrorCode, RecordedSaveError, ReplayStatus, } from '@reticlehq/core';
|
|
3
5
|
import { FlakeStore } from './flake-store.js';
|
|
4
6
|
import { ReticleTool } from '../tools/tool-names.js';
|
|
@@ -25,7 +27,7 @@ export { replayNamedFlow } from './flow-replay-run.js';
|
|
|
25
27
|
async function syncSavedFlowToCloud(deps, flow, projectId) {
|
|
26
28
|
// Per-project cloud: sync a saved flow only when cloud is attached AND flow sync is enabled.
|
|
27
29
|
const cloud = await resolveProjectCloud(deps.fs, deps.reticleRoot, homedir(), process.env);
|
|
28
|
-
if (cloud.config
|
|
30
|
+
if (null === cloud.config || !cloud.policy.flows)
|
|
29
31
|
return; // not attached / flows disabled → local only
|
|
30
32
|
const result = await syncFlowToCloud(flow, cloud.config, projectId, (url, init) => fetch(url, init));
|
|
31
33
|
if (result.outcome !== SyncOutcome.SYNCED) {
|
|
@@ -43,7 +45,7 @@ async function syncSavedFlowToCloud(deps, flow, projectId) {
|
|
|
43
45
|
export function leasableAppUrl(deps, sessionId) {
|
|
44
46
|
try {
|
|
45
47
|
const url = deps.sessions.resolve(sessionId).url;
|
|
46
|
-
if (typeof url !== 'string' || url.length
|
|
48
|
+
if (typeof url !== 'string' || 0 === url.length)
|
|
47
49
|
return undefined;
|
|
48
50
|
return new URL(url).origin;
|
|
49
51
|
}
|
|
@@ -70,6 +72,10 @@ export const FLOW_TOOLS = [
|
|
|
70
72
|
name: ReticleTool.FLOW_SAVE,
|
|
71
73
|
description: 'Persist the last/active recording (by name) as a git-checked, anchor-resolved flow at .reticle/flows/<name>.json. Each step is bound to a SEMANTIC anchor (testid/role/signal), never a volatile ref; steps without a resolvable testid are kept with degraded:true (a "add a data-testid here" marker) rather than dropped. Returns { name, stepCount, degraded, empty, assertions } — `assertions.grade` is asserted | presence-only | assertion-free: a flow that only acts (or only checks element presence) will pass even if the feature breaks, so when grade is not "asserted" follow assertions.warning and add a consequence assertion via reticle_annotate (assert-signal / assert-net / success-state).',
|
|
72
74
|
inputSchema: {
|
|
75
|
+
flow: z
|
|
76
|
+
.string()
|
|
77
|
+
.optional()
|
|
78
|
+
.describe('Alias for `flowName` (the name reticle_annotate uses).'),
|
|
73
79
|
flowName: z
|
|
74
80
|
.string()
|
|
75
81
|
.describe('Name for the flow file (saved to .reticle/flows/<flowName>.json). Use again in reticle_flow{action:"load"} / reticle_flow_replay.'),
|
|
@@ -103,7 +109,7 @@ export const FLOW_TOOLS = [
|
|
|
103
109
|
code: z.string().optional(),
|
|
104
110
|
},
|
|
105
111
|
handler: (deps, args) => {
|
|
106
|
-
const name = asString(args['flowName']) ?? '';
|
|
112
|
+
const name = asString(aliasParam(args, 'flowName', ['flow'])['flowName']) ?? '';
|
|
107
113
|
const program = deps.recordings.getCompiled(name);
|
|
108
114
|
if (program === undefined) {
|
|
109
115
|
return Promise.resolve({
|
|
@@ -121,6 +127,11 @@ export const FLOW_TOOLS = [
|
|
|
121
127
|
...(intent !== undefined ? { intent } : {}),
|
|
122
128
|
};
|
|
123
129
|
const projectId = sessionProjectId(deps, asString(args['sessionId']));
|
|
130
|
+
// A zero-step recording is not a flow. Saving it wrote a file the suite reports "unverifiable"
|
|
131
|
+
// forever while telling the agent it had saved a regression test — see empty-flow.
|
|
132
|
+
const emptyRefusal = emptyFlowRefusal(program.steps.length, name);
|
|
133
|
+
if (emptyRefusal !== undefined)
|
|
134
|
+
return Promise.resolve(emptyRefusal);
|
|
124
135
|
return deps.flows.save(program, annotations, projectId).then(async (res) => {
|
|
125
136
|
if (!res.ok)
|
|
126
137
|
return { error: flowErrorMessage(res.code), code: res.code };
|
|
@@ -170,6 +181,10 @@ export const FLOW_TOOLS = [
|
|
|
170
181
|
name: ReticleTool.FLOW_LOAD,
|
|
171
182
|
description: 'Read + validate a saved flow by flowName from .reticle/flows/<flowName>.json. Returns the FlowFile (version, flowName, createdAt, anchored steps) or a structured { error, code }.',
|
|
172
183
|
inputSchema: {
|
|
184
|
+
flow: z
|
|
185
|
+
.string()
|
|
186
|
+
.optional()
|
|
187
|
+
.describe('Alias for `flowName` (the name reticle_annotate uses).'),
|
|
173
188
|
flowName: z
|
|
174
189
|
.string()
|
|
175
190
|
.describe('Flow file name (without .json extension) from reticle_flow{action:"list"}.'),
|
|
@@ -179,13 +194,19 @@ export const FLOW_TOOLS = [
|
|
|
179
194
|
.describe('Active session ID — resolves the flow within that app’s scope.'),
|
|
180
195
|
},
|
|
181
196
|
outputSchema: {
|
|
197
|
+
flow: z
|
|
198
|
+
.string()
|
|
199
|
+
.optional()
|
|
200
|
+
.describe('Alias for `flowName` (the name reticle_annotate uses).'),
|
|
182
201
|
flowName: z.string(),
|
|
183
202
|
steps: z.array(z.unknown()),
|
|
184
203
|
createdAt: z.number().optional(),
|
|
185
204
|
},
|
|
186
205
|
handler: (deps, args) => {
|
|
187
206
|
const projectId = sessionProjectId(deps, asString(args['sessionId']));
|
|
188
|
-
return deps.flows
|
|
207
|
+
return deps.flows
|
|
208
|
+
.load(asString(aliasParam(args, 'flowName', ['flow'])['flowName']) ?? '', projectId)
|
|
209
|
+
.then((res) => {
|
|
189
210
|
if (!res.ok)
|
|
190
211
|
return { error: flowErrorMessage(res.code), code: res.code };
|
|
191
212
|
const { name, ...rest } = res.value;
|
|
@@ -199,6 +220,10 @@ export const FLOW_TOOLS = [
|
|
|
199
220
|
'to the connected app. Returns { deleted: true } or a structured { error, code } (code not_found ' +
|
|
200
221
|
'when no such flow — deleting an absent flow is an error, not a silent no-op).',
|
|
201
222
|
inputSchema: {
|
|
223
|
+
flow: z
|
|
224
|
+
.string()
|
|
225
|
+
.optional()
|
|
226
|
+
.describe('Alias for `flowName` (the name reticle_annotate uses).'),
|
|
202
227
|
flowName: z
|
|
203
228
|
.string()
|
|
204
229
|
.describe('Flow file name (without .json extension) from reticle_flow{action:"list"}.'),
|
|
@@ -214,7 +239,9 @@ export const FLOW_TOOLS = [
|
|
|
214
239
|
},
|
|
215
240
|
handler: (deps, args) => {
|
|
216
241
|
const projectId = sessionProjectId(deps, asString(args['sessionId']));
|
|
217
|
-
return deps.flows
|
|
242
|
+
return deps.flows
|
|
243
|
+
.remove(asString(aliasParam(args, 'flowName', ['flow'])['flowName']) ?? '', projectId)
|
|
244
|
+
.then((res) => {
|
|
218
245
|
if (!res.ok)
|
|
219
246
|
return { error: flowErrorMessage(res.code), code: res.code };
|
|
220
247
|
return { deleted: true };
|
|
@@ -230,6 +257,10 @@ export const FLOW_TOOLS = [
|
|
|
230
257
|
'Returns { name, status: ok|drift|error, steps:[...] }; missing/malformed files and action ' +
|
|
231
258
|
'failures are status:error with a structured code (distinct from contract-changed drift).',
|
|
232
259
|
inputSchema: {
|
|
260
|
+
flow: z
|
|
261
|
+
.string()
|
|
262
|
+
.optional()
|
|
263
|
+
.describe('Alias for `flowName` (the name reticle_annotate uses).'),
|
|
233
264
|
flowName: z
|
|
234
265
|
.string()
|
|
235
266
|
.describe('Flow file name (without .json extension) from reticle_flow{action:"list"}.'),
|
|
@@ -311,7 +342,7 @@ export const FLOW_TOOLS = [
|
|
|
311
342
|
// "Replay all" means all of THIS app's flows (+ legacy), not every project's on a shared daemon.
|
|
312
343
|
const projectId = sessionProjectId(deps, sessionId);
|
|
313
344
|
const requested = Array.isArray(args['names'])
|
|
314
|
-
? args['names'].filter((n) => typeof n
|
|
345
|
+
? args['names'].filter((n) => 'string' === typeof n)
|
|
315
346
|
: await deps.flows.list(projectId);
|
|
316
347
|
// verify:server — hand the whole suite to the hosted runner; it records the verification itself.
|
|
317
348
|
const cloud = await resolveProjectCloud(deps.fs, deps.reticleRoot, homedir(), process.env);
|
|
@@ -345,12 +376,18 @@ export const FLOW_TOOLS = [
|
|
|
345
376
|
await lease.release().catch(() => undefined);
|
|
346
377
|
}
|
|
347
378
|
});
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
379
|
+
const parallelRuns = await Promise.all(outcomes.map(async (o, i) => {
|
|
380
|
+
const name = requested[i] ?? '';
|
|
381
|
+
const replay = o.ok && o.value !== undefined ? o.value : leaseFailureReplay(name, o.error);
|
|
382
|
+
const loaded = await deps.flows.load(name, projectId).catch(() => null);
|
|
383
|
+
const flow = loaded !== null && loaded.ok ? loaded.value : undefined;
|
|
384
|
+
return flow === undefined ? { replay } : { replay, flow };
|
|
385
|
+
}));
|
|
386
|
+
return buildSuiteVerdict(parallelRuns);
|
|
353
387
|
}
|
|
388
|
+
// The flow FILE travels with each replay so the verdict can tell a green that verified
|
|
389
|
+
// something from a green that could never have gone red. Without it the suite reported "all 1
|
|
390
|
+
// flow pass" for a flow with no steps at all.
|
|
354
391
|
const runs = [];
|
|
355
392
|
const timed = [];
|
|
356
393
|
// The flake ledger the CLI gate already keeps — see FlakeStore. It was only ever written by
|
|
@@ -362,7 +399,9 @@ export const FLOW_TOOLS = [
|
|
|
362
399
|
for (const flowName of requested) {
|
|
363
400
|
const start = deps.now();
|
|
364
401
|
const replay = await replayNamedFlow(deps, { flowName, sessionId });
|
|
365
|
-
|
|
402
|
+
const loaded = await deps.flows.load(flowName, projectId).catch(() => null);
|
|
403
|
+
const flow = loaded !== null && loaded.ok ? loaded.value : undefined;
|
|
404
|
+
runs.push(flow === undefined ? { replay } : { replay, flow });
|
|
366
405
|
timed.push({ replay, durationMs: deps.now() - start });
|
|
367
406
|
}
|
|
368
407
|
// Accrue this run's outcomes, then report what the ledger knows. Best-effort on both halves: a
|
|
@@ -389,6 +428,10 @@ export const FLOW_TOOLS = [
|
|
|
389
428
|
'recorded name. Returns { name, stepCount, degraded, empty } or { error, code } (code ' +
|
|
390
429
|
'flow_no_recorded when no recording is present).',
|
|
391
430
|
inputSchema: {
|
|
431
|
+
flow: z
|
|
432
|
+
.string()
|
|
433
|
+
.optional()
|
|
434
|
+
.describe('Alias for `flowName` (the name reticle_annotate uses).'),
|
|
392
435
|
flowName: z
|
|
393
436
|
.string()
|
|
394
437
|
.optional()
|
|
@@ -419,10 +462,13 @@ export const FLOW_TOOLS = [
|
|
|
419
462
|
code: RecordedSaveError.NO_RECORDED_FLOW,
|
|
420
463
|
};
|
|
421
464
|
}
|
|
422
|
-
const override = asString(args['flowName']);
|
|
465
|
+
const override = asString(aliasParam(args, 'flowName', ['flow'])['flowName']);
|
|
423
466
|
const flow = override !== undefined ? { ...recorded.flow, name: override } : recorded.flow;
|
|
424
467
|
// The store stamps the project into the file AND routes it to the per-project subdir (a shared
|
|
425
468
|
// daemon serves many apps), so location and content agree from one source of truth.
|
|
469
|
+
const emptyRecorded = emptyFlowRefusal(flow.steps.length, flow.name);
|
|
470
|
+
if (emptyRecorded !== undefined)
|
|
471
|
+
return emptyRecorded;
|
|
426
472
|
const res = await deps.flows.saveFlow(flow, session.projectId);
|
|
427
473
|
if (!res.ok)
|
|
428
474
|
return { error: flowErrorMessage(res.code), code: res.code };
|
|
@@ -446,6 +492,10 @@ export const FLOW_TOOLS = [
|
|
|
446
492
|
'untouched). Returns { name, status: healed|drift|unhealable|consequence_broken|' +
|
|
447
493
|
'nothing_to_heal|error, applied, proposals[], changed[], message }.',
|
|
448
494
|
inputSchema: {
|
|
495
|
+
flow: z
|
|
496
|
+
.string()
|
|
497
|
+
.optional()
|
|
498
|
+
.describe('Alias for `flowName` (the name reticle_annotate uses).'),
|
|
449
499
|
flowName: z.string().describe('Flow file name to heal (from reticle_flow{action:"list"}).'),
|
|
450
500
|
apply: z.boolean().optional(),
|
|
451
501
|
confirmDangerous: z
|
|
@@ -458,6 +508,10 @@ export const FLOW_TOOLS = [
|
|
|
458
508
|
.describe('Active session ID from reticle_sessions. Omit when only one browser session is open.'),
|
|
459
509
|
},
|
|
460
510
|
outputSchema: {
|
|
511
|
+
flow: z
|
|
512
|
+
.string()
|
|
513
|
+
.optional()
|
|
514
|
+
.describe('Alias for `flowName` (the name reticle_annotate uses).'),
|
|
461
515
|
flowName: z.string(),
|
|
462
516
|
status: z.string(),
|
|
463
517
|
applied: z.boolean(),
|
package/dist/flows/flows.js
CHANGED
|
@@ -28,10 +28,10 @@ function sourceArg(src) {
|
|
|
28
28
|
const source = asRecord(src['source']);
|
|
29
29
|
const file = source['file'];
|
|
30
30
|
const line = source['line'];
|
|
31
|
-
if (typeof file !== 'string' || file.length
|
|
31
|
+
if (typeof file !== 'string' || 0 === file.length || typeof line !== 'number')
|
|
32
32
|
return undefined;
|
|
33
33
|
const out = { file, line };
|
|
34
|
-
if (typeof source['column']
|
|
34
|
+
if ('number' === typeof source['column'])
|
|
35
35
|
out.column = source['column'];
|
|
36
36
|
return out;
|
|
37
37
|
}
|
|
@@ -109,8 +109,12 @@ export function recordedStepToFlowStep(step) {
|
|
|
109
109
|
if (step.tool === ReticleTool.ACT_SEQUENCE) {
|
|
110
110
|
const rawSubs = Array.isArray(step.args['steps']) ? step.args['steps'] : [];
|
|
111
111
|
const subs = rawSubs.map(subStepToFlowStep);
|
|
112
|
-
const degraded = subs.some((s) => s.degraded
|
|
113
|
-
|
|
112
|
+
const degraded = subs.some((s) => true === s.degraded);
|
|
113
|
+
// The first sub-step that HAS an anchor, not blindly the first. Taking subs[0] handed the whole
|
|
114
|
+
// sequence the degraded sentinel whenever sub-step 0 lacked a testid — even with every later
|
|
115
|
+
// sub-step perfectly anchored — and replay then queried the DOM for a testid literally named
|
|
116
|
+
// "unresolved", so the step drifted on all 5 apps, every replay.
|
|
117
|
+
const anchor = subs.find((s) => s.degraded !== true)?.anchor ?? subs[0]?.anchor ?? degradedAnchor();
|
|
114
118
|
const out = { tool: ReticleTool.ACT_SEQUENCE, anchor, steps: subs };
|
|
115
119
|
if (degraded)
|
|
116
120
|
out.degraded = true;
|
|
@@ -185,14 +189,14 @@ export class FlowStore {
|
|
|
185
189
|
const flow = withAnnotations(base, annotations);
|
|
186
190
|
await this.#fs.mkdir(flowDir(this.#root, pid));
|
|
187
191
|
await this.#fs.writeFile(flowPath(this.#root, program.name, pid), this.#serialize(flow));
|
|
188
|
-
const degraded = flow.steps.filter((s) => s.degraded
|
|
192
|
+
const degraded = flow.steps.filter((s) => true === s.degraded).length;
|
|
189
193
|
return {
|
|
190
194
|
ok: true,
|
|
191
195
|
value: {
|
|
192
196
|
name: program.name,
|
|
193
197
|
stepCount: flow.steps.length,
|
|
194
198
|
degraded,
|
|
195
|
-
empty: flow.steps.length
|
|
199
|
+
empty: 0 === flow.steps.length,
|
|
196
200
|
},
|
|
197
201
|
};
|
|
198
202
|
}
|
|
@@ -214,14 +218,14 @@ export class FlowStore {
|
|
|
214
218
|
const valid = parsed.data;
|
|
215
219
|
await this.#fs.mkdir(flowDir(this.#root, pid));
|
|
216
220
|
await this.#fs.writeFile(flowPath(this.#root, asFlowName(valid.name), pid), this.#serialize(valid));
|
|
217
|
-
const degraded = valid.steps.filter((s) => s.degraded
|
|
221
|
+
const degraded = valid.steps.filter((s) => true === s.degraded).length;
|
|
218
222
|
return {
|
|
219
223
|
ok: true,
|
|
220
224
|
value: {
|
|
221
225
|
name: valid.name,
|
|
222
226
|
stepCount: valid.steps.length,
|
|
223
227
|
degraded,
|
|
224
|
-
empty: valid.steps.length
|
|
228
|
+
empty: 0 === valid.steps.length,
|
|
225
229
|
},
|
|
226
230
|
};
|
|
227
231
|
}
|
|
@@ -247,7 +251,7 @@ export class FlowStore {
|
|
|
247
251
|
// Write back to the SAME file load resolved (nested if it lives there, else legacy flat), so a
|
|
248
252
|
// heal never forks a second copy and byte-stability holds regardless of where the flow lives.
|
|
249
253
|
const path = await this.#resolveReadPath(name, pid);
|
|
250
|
-
if (
|
|
254
|
+
if (null === path)
|
|
251
255
|
return { ok: false, code: FlowErrorCode.NOT_FOUND };
|
|
252
256
|
const { flow: next, applied } = applyHealChanges(flow, changes);
|
|
253
257
|
await this.#fs.writeFile(path, this.#serialize(next));
|
|
@@ -343,7 +347,7 @@ export class FlowStore {
|
|
|
343
347
|
if (!isValidFlowName(name))
|
|
344
348
|
return { ok: false, code: FlowErrorCode.INVALID_NAME };
|
|
345
349
|
const path = await this.#resolveReadPath(name, safeProjectId(projectId));
|
|
346
|
-
if (
|
|
350
|
+
if (null === path)
|
|
347
351
|
return { ok: false, code: FlowErrorCode.NOT_FOUND };
|
|
348
352
|
let text;
|
|
349
353
|
try {
|
|
@@ -377,7 +381,7 @@ export class FlowStore {
|
|
|
377
381
|
if (!isValidFlowName(name))
|
|
378
382
|
return { ok: false, code: FlowErrorCode.INVALID_NAME };
|
|
379
383
|
const path = await this.#resolveReadPath(name, safeProjectId(projectId));
|
|
380
|
-
if (
|
|
384
|
+
if (null === path)
|
|
381
385
|
return { ok: false, code: FlowErrorCode.NOT_FOUND };
|
|
382
386
|
await this.#fs.rm(path);
|
|
383
387
|
return { ok: true, value: undefined };
|
package/dist/flows/gate.d.ts
CHANGED
|
@@ -6,12 +6,12 @@
|
|
|
6
6
|
* inputs (git diff → affected → run artifacts + flake ledger) and maps `pass` to the exit code.
|
|
7
7
|
*/
|
|
8
8
|
/** A flow whose assertions were WEAKENED since its last passing run. */
|
|
9
|
-
|
|
9
|
+
interface DowngradedFlow {
|
|
10
10
|
flow: string;
|
|
11
11
|
/** Step indices whose mustHold dropped from a consequence to presence-only. */
|
|
12
12
|
steps: number[];
|
|
13
13
|
}
|
|
14
|
-
|
|
14
|
+
interface GateInput {
|
|
15
15
|
/** Flows that must be covered (from the affected index over the changed files). */
|
|
16
16
|
affected: readonly string[];
|
|
17
17
|
/** Flows that have a passing verification artifact. */
|
|
@@ -23,7 +23,7 @@ export interface GateInput {
|
|
|
23
23
|
/** Flows that covered a changed file but no longer exist — coverage deleted rather than satisfied. */
|
|
24
24
|
deleted?: readonly string[];
|
|
25
25
|
}
|
|
26
|
-
|
|
26
|
+
interface GateResult {
|
|
27
27
|
/** True when every affected, non-flaky flow has a passing artifact AND nothing was weakened/deleted. */
|
|
28
28
|
pass: boolean;
|
|
29
29
|
/** Affected flows with no passing artifact and not flaky — these block the gate. */
|
|
@@ -36,3 +36,4 @@ export interface GateResult {
|
|
|
36
36
|
deleted: string[];
|
|
37
37
|
}
|
|
38
38
|
export declare function gateDecision(input: GateInput): GateResult;
|
|
39
|
+
export {};
|
package/dist/flows/gate.js
CHANGED
|
@@ -24,7 +24,7 @@ export function gateDecision(input) {
|
|
|
24
24
|
const downgraded = [...(input.downgraded ?? [])];
|
|
25
25
|
const deleted = [...(input.deleted ?? [])];
|
|
26
26
|
return {
|
|
27
|
-
pass: uncovered.length
|
|
27
|
+
pass: 0 === uncovered.length && 0 === downgraded.length && 0 === deleted.length,
|
|
28
28
|
uncovered,
|
|
29
29
|
quarantined,
|
|
30
30
|
downgraded,
|
package/dist/flows/heal-run.d.ts
CHANGED
|
@@ -7,12 +7,4 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import { type FlowHealResult } from '@reticlehq/core';
|
|
9
9
|
import type { ToolDeps } from '../tools/tools.js';
|
|
10
|
-
export declare const HEAL_MESSAGES: {
|
|
11
|
-
readonly NOTHING: "nothing to heal — every anchor resolved on replay";
|
|
12
|
-
readonly HEALED: "rewrote drifted testid anchors to their nearest surviving match and re-verified the flow's success consequence still fires";
|
|
13
|
-
readonly DRIFT_DRY: "confident rebind(s) proposed — re-run with apply:true to write them to disk";
|
|
14
|
-
readonly UNHEALABLE: "drift found, but no nearest match cleared the confidence floor (HEAL_CONFIDENCE_MIN=0.5); file left untouched — add a data-testid or fix the flow by hand";
|
|
15
|
-
readonly HEALED_UNVERIFIED: "rewrote drifted testid anchors — but this flow declares no success consequence, so the rebind resolves a locator without proving the intent still holds. Add a success-state assertion (reticle_annotate) so future heals can be verified.";
|
|
16
|
-
readonly CONSEQUENCE_BROKEN: "rebind resolves the drifted locator to a surviving element, but the healed flow no longer satisfies its success consequence — refusing to write (a heal that loses the intent would ship a green-but-dead test). Fix by hand and verify";
|
|
17
|
-
};
|
|
18
10
|
export declare function healFlow(deps: ToolDeps, args: Record<string, unknown>): Promise<FlowHealResult>;
|