@reticlehq/server 3.3.0 → 3.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/LICENSE-ENTERPRISE +1 -1
- package/README.md +3 -3
- package/SKILL.md +12 -14
- package/dist/chromium-launch-options.d.ts +4 -3
- package/dist/chromium-launch-options.js +6 -2
- package/dist/command/cli/adhoc-suite.d.ts +22 -0
- package/dist/command/cli/adhoc-suite.js +90 -19
- package/dist/command/cli/adhoc-verdict.d.ts +35 -0
- package/dist/command/cli/adhoc-verdict.js +90 -3
- package/dist/command/cli/answers/gate-hook-message.d.ts +6 -0
- package/dist/command/cli/answers/gate-hook-message.js +6 -0
- package/dist/command/cli/auth/cloud-keystore.d.ts +0 -2
- package/dist/command/cli/auth/cloud-keystore.js +0 -3
- package/dist/command/cli/auth/cloud-session.d.ts +6 -2
- package/dist/command/cli/auth/cloud-session.js +2 -0
- package/dist/command/cli/auth/device-flow.d.ts +2 -2
- package/dist/command/cli/cli-doctor.js +7 -5
- package/dist/command/cli/cli-flow-commands.d.ts +3 -6
- package/dist/command/cli/cli-flow-commands.js +67 -4
- package/dist/command/cli/cli-parse.d.ts +8 -1
- package/dist/command/cli/cli-parse.js +24 -4
- package/dist/command/cli/cli-update-commands.js +8 -1
- package/dist/command/cli/cli-verify.d.ts +24 -3
- package/dist/command/cli/cli-verify.js +54 -11
- package/dist/command/cli/cloud-cli.js +64 -2
- package/dist/command/cli/cloud-kit.js +3 -5
- package/dist/command/cli/cloud-login.d.ts +1 -1
- package/dist/command/cli/cloud-login.js +27 -8
- package/dist/command/cli/demo/demo-page.js +6 -3
- package/dist/command/cli/doctor/browser/chromium-hint.d.ts +5 -0
- package/dist/command/cli/doctor/browser/chromium-hint.js +5 -0
- package/dist/command/cli/launch/cli-launch.d.ts +8 -0
- package/dist/command/cli/launch/cli-launch.js +21 -9
- package/dist/command/cli/launch/sdk-version-match.d.ts +84 -0
- package/dist/command/cli/launch/sdk-version-match.js +206 -0
- package/dist/command/cli/lifecycle/daemon-lifecycle.js +2 -16
- package/dist/command/cli/ports/resolve/cli-port.d.ts +25 -0
- package/dist/command/cli/ports/resolve/cli-port.js +125 -15
- package/dist/command/cli/setup-mcp-cli.js +21 -13
- package/dist/command/cli/tutorial.d.ts +2 -2
- package/dist/command/cli/tutorial.js +21 -7
- package/dist/command/cli.js +79 -11
- package/dist/command/daemon/binding/daemon-status-probe.d.ts +14 -2
- package/dist/command/daemon/binding/daemon-status-probe.js +34 -6
- package/dist/command/daemon/daemon-resolve.d.ts +9 -0
- package/dist/command/daemon/daemon-resolve.js +1 -1
- package/dist/command/daemon/lifetime/daemon-usefulness.d.ts +7 -1
- package/dist/command/daemon/lifetime/daemon-usefulness.js +30 -7
- package/dist/command/dev/stale-issue-guard.d.ts +3 -4
- package/dist/command/dev/stale-issue-guard.js +8 -8
- package/dist/command/setup/bringup/connect-lease.d.ts +17 -0
- package/dist/command/setup/bringup/connect-lease.js +38 -0
- package/dist/command/setup/bringup/dev-server-files.d.ts +68 -0
- package/dist/command/setup/bringup/dev-server-files.js +131 -0
- package/dist/command/setup/bringup/dev-server-log-cap.d.ts +35 -0
- package/dist/command/setup/bringup/dev-server-log-cap.js +121 -0
- package/dist/command/setup/bringup/dev-server-wait.d.ts +2 -0
- package/dist/command/setup/bringup/dev-server-wait.js +1 -1
- package/dist/command/setup/bringup/ensure-daemon.d.ts +65 -4
- package/dist/command/setup/bringup/ensure-daemon.js +135 -16
- package/dist/command/setup/bringup/static-page-server.d.ts +35 -0
- package/dist/command/setup/bringup/static-page-server.js +151 -0
- package/dist/command/setup/desktop-shape.d.ts +4 -0
- package/dist/command/setup/desktop-shape.js +18 -2
- package/dist/command/setup/init/init-port.d.ts +15 -0
- package/dist/command/setup/init/init-port.js +44 -0
- package/dist/command/setup/init/init-runtime.d.ts +1 -1
- package/dist/command/setup/init/init-runtime.js +91 -9
- package/dist/command/setup/node-effects.d.ts +27 -14
- package/dist/command/setup/node-effects.js +86 -45
- package/dist/command/setup/probe/loopback-probe-urls.d.ts +7 -0
- package/dist/command/setup/probe/loopback-probe-urls.js +40 -0
- package/dist/command/setup/probe/page-probe.d.ts +13 -2
- package/dist/command/setup/probe/page-probe.js +18 -3
- package/dist/command/setup/run-setup.d.ts +28 -1
- package/dist/command/setup/run-setup.js +103 -15
- package/dist/command/setup/session-pick.d.ts +13 -0
- package/dist/command/setup/session-pick.js +20 -0
- package/dist/command/setup/setup-command.d.ts +2 -14
- package/dist/command/setup/setup-command.js +105 -31
- package/dist/command/setup/setup-mcp.d.ts +2 -0
- package/dist/command/setup/setup-mcp.js +17 -8
- package/dist/command/setup/terminal/wait-progress.d.ts +8 -0
- package/dist/command/setup/terminal/wait-progress.js +17 -0
- package/dist/command/version/peer-announce.d.ts +6 -2
- package/dist/command/version/peer-announce.js +8 -6
- package/dist/command/version/version-nudge.d.ts +17 -3
- package/dist/command/version/version-nudge.js +25 -4
- package/dist/features/crawl/crawl-empty.js +1 -1
- package/dist/features/crawl/crawl-tools.d.ts +6 -0
- package/dist/features/crawl/crawl-tools.js +57 -0
- package/dist/features/crawl/crawl.js +171 -157
- package/dist/features/exhaust/app-coverage.d.ts +33 -0
- package/dist/features/exhaust/app-coverage.js +31 -0
- package/dist/features/exhaust/code-coverage.d.ts +47 -0
- package/dist/features/exhaust/code-coverage.js +77 -0
- package/dist/features/exhaust/exhaust-run.d.ts +33 -0
- package/dist/features/exhaust/exhaust-run.js +82 -0
- package/dist/features/exhaust/explorer.d.ts +103 -0
- package/dist/features/exhaust/explorer.js +195 -0
- package/dist/features/exhaust/failure-branches.d.ts +33 -0
- package/dist/features/exhaust/failure-branches.js +92 -0
- package/dist/features/exhaust/ledger.d.ts +202 -0
- package/dist/features/exhaust/ledger.js +252 -0
- package/dist/features/exhaust/session-fold.d.ts +31 -0
- package/dist/features/exhaust/session-fold.js +79 -0
- package/dist/features/exhaust/session-port.d.ts +36 -0
- package/dist/features/exhaust/session-port.js +102 -0
- package/dist/features/harness/drive-report.d.ts +0 -9
- package/dist/features/harness/drive-report.js +18 -0
- package/dist/features/harness/driver.js +4 -7
- package/dist/features/harness/fill-values.d.ts +10 -0
- package/dist/features/harness/fill-values.js +50 -11
- package/dist/features/harness/jev-driver.js +30 -13
- package/dist/features/harness/openai-driver.d.ts +12 -0
- package/dist/features/harness/openai-driver.js +21 -12
- package/dist/features/harness/platform-config.js +5 -6
- package/dist/features/license/license.d.ts +2 -2
- package/dist/index.d.ts +9 -0
- package/dist/index.js +56 -19
- package/dist/judgement/capsule/capsule-store.d.ts +4 -4
- package/dist/judgement/domain/domain-model.js +3 -1
- package/dist/judgement/oracles/self-instrument.js +4 -2
- package/dist/judgement/runs/artifact/replay-mapping.d.ts +2 -2
- package/dist/judgement/runs/artifact/replay-mapping.js +44 -9
- package/dist/judgement/runs/verification-sync.d.ts +3 -1
- package/dist/judgement/runs/verification-sync.js +1 -1
- package/dist/language/flows/change/affected.d.ts +9 -0
- package/dist/language/flows/change/affected.js +35 -0
- package/dist/language/flows/change/gate.d.ts +20 -0
- package/dist/language/flows/change/gate.js +12 -1
- package/dist/language/flows/flow-author.d.ts +7 -0
- package/dist/language/flows/flow-author.js +19 -0
- package/dist/language/flows/flow-expect-grammar.js +1 -1
- package/dist/language/flows/flow-journey.d.ts +54 -0
- package/dist/language/flows/flow-journey.js +119 -0
- package/dist/language/flows/flow-replay-run.d.ts +2 -3
- package/dist/language/flows/flow-replay-run.js +13 -47
- package/dist/language/flows/flow-replay.d.ts +22 -8
- package/dist/language/flows/flow-replay.js +55 -9
- package/dist/language/flows/flow-result.d.ts +2 -0
- package/dist/language/flows/flow-result.js +25 -0
- package/dist/language/flows/flow-tools.js +17 -5
- package/dist/language/flows/flows.d.ts +10 -1
- package/dist/language/flows/flows.js +90 -14
- package/dist/language/flows/recording/replay-halt.js +3 -1
- package/dist/language/flows/recording/tape/recordings.d.ts +30 -1
- package/dist/language/flows/recording/tape/recordings.js +55 -1
- package/dist/language/flows/replay.d.ts +19 -0
- package/dist/language/flows/replay.js +42 -6
- package/dist/language/flows/verify-change-tools.js +5 -2
- package/dist/launch-chromium.d.ts +80 -0
- package/dist/launch-chromium.js +218 -0
- package/dist/memory/cloud/cloud-config.d.ts +11 -2
- package/dist/memory/cloud/cloud-config.js +47 -7
- package/dist/memory/cloud/cloud-sync.d.ts +4 -1
- package/dist/memory/cloud/cloud-sync.js +6 -9
- package/dist/memory/cloud/harness-offer.js +5 -6
- package/dist/memory/cloud/harness-switch.js +5 -6
- package/dist/memory/cloud/sync-cycle.d.ts +77 -3
- package/dist/memory/cloud/sync-cycle.js +325 -55
- package/dist/memory/cloud/sync-daemon.js +24 -20
- package/dist/memory/cloud/sync-disk.js +16 -2
- package/dist/memory/intent/intent-shard.d.ts +10 -32
- package/dist/memory/intent/intent-shard.js +0 -10
- package/dist/memory/intent/intent-tools.js +8 -3
- package/dist/memory/journal/drive-flow.d.ts +18 -13
- package/dist/memory/journal/drive-flow.js +45 -37
- package/dist/memory/journal/envelope.d.ts +8 -8
- package/dist/memory/journal/journal-query.d.ts +14 -0
- package/dist/memory/journal/journal-query.js +15 -0
- package/dist/memory/journal/on-disk/workspace-tiers.js +2 -0
- package/dist/memory/journal/session-end.d.ts +9 -1
- package/dist/memory/journal/session-end.js +24 -12
- package/dist/memory/project/artifact-root-resolver.d.ts +10 -0
- package/dist/memory/project/artifact-root-resolver.js +46 -19
- package/dist/memory/project/dir/reticle-dir.d.ts +2 -0
- package/dist/memory/project/dir/reticle-dir.js +1 -0
- package/dist/memory/project/project-tools.js +3 -1
- package/dist/portal/bridge/auth-failure-reason.d.ts +1 -1
- package/dist/portal/bridge/auth-failure-reason.js +4 -2
- package/dist/portal/bridge/bridge.d.ts +12 -3
- package/dist/portal/bridge/bridge.js +49 -3
- package/dist/portal/input/js-coverage.d.ts +25 -0
- package/dist/portal/input/js-coverage.js +30 -0
- package/dist/portal/input/real-input.d.ts +11 -0
- package/dist/portal/input/real-input.js +29 -4
- package/dist/portal/input/viewport-tools.js +16 -6
- package/dist/portal/pool/playwright-launcher.d.ts +6 -0
- package/dist/portal/pool/playwright-launcher.js +20 -13
- package/dist/portal/session/dev-server/dev-command.d.ts +2 -2
- package/dist/portal/session/dev-server/dev-command.js +30 -12
- package/dist/portal/session/dev-server/dev-server-probe.js +17 -1
- package/dist/portal/session/dev-server/route-status-probe.d.ts +24 -0
- package/dist/portal/session/dev-server/route-status-probe.js +57 -0
- package/dist/portal/session/facts/observed-state.d.ts +11 -0
- package/dist/portal/session/facts/observed-state.js +34 -0
- package/dist/portal/session/no-session-diagnosis.d.ts +29 -0
- package/dist/portal/session/no-session-diagnosis.js +62 -5
- package/dist/portal/session/no-session-next-action.d.ts +13 -0
- package/dist/portal/session/no-session-next-action.js +48 -9
- package/dist/portal/session/no-session-watch.d.ts +18 -0
- package/dist/portal/session/no-session-watch.js +88 -2
- package/dist/portal/session/session-health.js +3 -2
- package/dist/portal/session/session-manager.d.ts +20 -6
- package/dist/portal/session/session-manager.js +19 -3
- package/dist/portal/session/session-reaper.d.ts +28 -0
- package/dist/portal/session/session-reaper.js +49 -1
- package/dist/portal/session/session.d.ts +10 -8
- package/dist/portal/session/session.js +24 -32
- package/dist/surface/http-server.d.ts +3 -2
- package/dist/surface/http-server.js +8 -5
- package/dist/surface/mcp/mcp-proxy.d.ts +2 -2
- package/dist/surface/mcp/mcp-proxy.js +2 -2
- package/dist/surface/mcp/mcp.js +5 -3
- package/dist/surface/mcp/proxy/proxy-daemon-probe.d.ts +13 -1
- package/dist/surface/mcp/proxy/proxy-daemon-probe.js +27 -2
- package/dist/surface/mcp/proxy/proxy-log.js +3 -5
- package/dist/surface/mcp/server-instructions.js +9 -7
- package/dist/surface/tools/act/after-match.d.ts +17 -0
- package/dist/surface/tools/act/after-match.js +30 -0
- package/dist/surface/tools/act/durable.d.ts +69 -0
- package/dist/surface/tools/act/durable.js +139 -0
- package/dist/surface/tools/act/navigation/navigate-arrival.d.ts +13 -6
- package/dist/surface/tools/act/navigation/navigate-arrival.js +47 -10
- package/dist/surface/tools/act/navigation/navigate-result.d.ts +1 -12
- package/dist/surface/tools/act/navigation/navigate-result.js +21 -3
- package/dist/surface/tools/act/proved-controls.d.ts +9 -0
- package/dist/surface/tools/act/proved-controls.js +11 -0
- package/dist/surface/tools/act/sequence-grade.d.ts +5 -0
- package/dist/surface/tools/act/sequence-grade.js +32 -1
- package/dist/surface/tools/act-capsule.d.ts +7 -0
- package/dist/surface/tools/act-capsule.js +46 -1
- package/dist/surface/tools/act-sequence-tool.js +25 -8
- package/dist/surface/tools/act-tools.js +28 -52
- package/dist/surface/tools/assert/assert-grade.js +12 -1
- package/dist/surface/tools/assert/assert-verdict.js +2 -2
- package/dist/surface/tools/browser-tools.js +14 -2
- package/dist/surface/tools/coverage-tools.js +27 -1
- package/dist/surface/tools/dynamic-tools.js +29 -2
- package/dist/surface/tools/error-recovery.js +5 -6
- package/dist/surface/tools/feedback-invite.js +6 -4
- package/dist/surface/tools/harness-explore.d.ts +3 -2
- package/dist/surface/tools/harness-explore.js +12 -7
- package/dist/surface/tools/invoke-tool.js +12 -8
- package/dist/surface/tools/lease-tools.js +15 -6
- package/dist/surface/tools/live-call-text.js +4 -1
- package/dist/surface/tools/observe-tools.js +9 -1
- package/dist/surface/tools/tool-kit.d.ts +10 -0
- package/dist/surface/tools/tools.js +4 -0
- package/dist/telemetry/argument-shape.d.ts +0 -2
- package/dist/telemetry/argument-shape.js +1 -5
- package/dist/telemetry/error-class.d.ts +2 -3
- package/dist/telemetry/error-class.js +2 -3
- package/dist/telemetry/harness-drive.d.ts +0 -2
- package/dist/telemetry/harness-drive.js +0 -4
- package/dist/telemetry/onboarding-firsts.d.ts +2 -2
- package/dist/telemetry/onboarding-firsts.js +2 -2
- package/dist/wire-journal.d.ts +3 -0
- package/dist/wire-journal.js +4 -0
- package/dist/workspace-packages.d.ts +0 -2
- package/dist/workspace-packages.js +1 -9
- package/docs/README.md +4 -2
- package/docs/actions.mdx +3 -4
- package/docs/agent-cheatsheet.md +4 -4
- package/docs/architecture.md +6 -6
- package/docs/best-practices.mdx +1 -1
- package/docs/capabilities.mdx +32 -1
- package/docs/cli/cloud.mdx +20 -8
- package/docs/cli/doctor.mdx +4 -0
- package/docs/cli/feedback.mdx +2 -2
- package/docs/cli/gate.mdx +9 -4
- package/docs/cli/update.mdx +10 -0
- package/docs/cli.mdx +22 -7
- package/docs/desktop.mdx +14 -10
- package/docs/environment.mdx +87 -0
- package/docs/faq.mdx +18 -16
- package/docs/first-drive.md +5 -13
- package/docs/flows.md +21 -2
- package/docs/frameworks.mdx +29 -10
- package/docs/getting-started.md +15 -5
- package/docs/http-transport.md +6 -3
- package/docs/human-control.md +2 -2
- package/docs/index.mdx +16 -6
- package/docs/install-agentic.mdx +2 -2
- package/docs/install-manual.mdx +1 -1
- package/docs/integration-patterns.md +3 -3
- package/docs/multi-agent-testing.md +1 -1
- package/docs/packages/babel-plugin.mdx +1 -1
- package/docs/packages/browser.mdx +2 -2
- package/docs/packages/next.mdx +3 -0
- package/docs/packages/react.mdx +2 -2
- package/docs/packages/tauri.mdx +1 -1
- package/docs/packages/vite-plugin.mdx +4 -2
- package/docs/packages.mdx +9 -9
- package/docs/predicates.mdx +50 -5
- package/docs/quickstart.mdx +12 -10
- package/docs/recipes.mdx +2 -2
- package/docs/state-management.mdx +2 -2
- package/docs/system-map.md +1 -1
- package/docs/telemetry.md +1 -1
- package/docs/token-efficiency.md +3 -3
- package/docs/tools/act-and-wait.mdx +1 -0
- package/docs/tools/act-sequence.mdx +1 -1
- package/docs/tools/act.mdx +1 -1
- package/docs/tools/console.mdx +1 -1
- package/docs/tools/inspect.mdx +1 -1
- package/docs/tools/network.mdx +1 -1
- package/docs/tools/query.mdx +1 -1
- package/docs/tools/sessions.mdx +1 -1
- package/docs/tools/snapshot.mdx +1 -1
- package/docs/tools/state.mdx +1 -1
- package/docs/tools/wait-for.mdx +1 -1
- package/docs/usage.md +7 -7
- package/docs/use-cases.mdx +157 -0
- package/docs/vs-screenshots.mdx +3 -3
- package/docs/what-is-recorded.md +2 -2
- package/docs/what-is-reticle.mdx +17 -20
- package/docs/why-reticle.mdx +14 -21
- package/package.json +10 -9
- package/dist/judgement/capsule/minimize.d.ts +0 -12
- package/dist/judgement/capsule/minimize.js +0 -22
- package/dist/judgement/phenomena/phenomena.d.ts +0 -27
- package/dist/judgement/phenomena/phenomena.js +0 -101
- package/dist/language/flows/flow-report.d.ts +0 -22
- package/dist/language/flows/flow-report.js +0 -106
package/docs/vs-screenshots.mdx
CHANGED
|
@@ -80,11 +80,11 @@ Those are real bugs and structural reads can miss them. Reticle has `reticle_scr
|
|
|
80
80
|
|
|
81
81
|
The argument is not "screenshots are bad". It is that a screenshot cannot answer "did the code work", and that is the question an AI coding agent needs answered on every change.
|
|
82
82
|
|
|
83
|
-
##
|
|
83
|
+
## What is measured, and what is not
|
|
84
84
|
|
|
85
|
-
|
|
85
|
+
What is measured is detection: 10 of 10 injected regressions caught with zero false alarms, against 9 and 8 for the alternatives, and one false green against a Playwright script's 29 over an 88-bug registry.
|
|
86
86
|
|
|
87
|
-
What is
|
|
87
|
+
What is not established is that Reticle makes agents _fix_ more bugs. A fix-loop test on a small fixture found the same fix rate with and without it; [Why Reticle](/why-reticle#what-the-fix-rate-test-measured) says what that test could and could not show.
|
|
88
88
|
|
|
89
89
|
## FAQ
|
|
90
90
|
|
package/docs/what-is-recorded.md
CHANGED
|
@@ -5,7 +5,7 @@ description: What Reticle keeps on your machine, what is redacted, and what it t
|
|
|
5
5
|
|
|
6
6
|
Reticle drives your app and reads what it did. That means it holds real content from real pages, so this page says exactly what it keeps, where it keeps it, and what it takes to make any of it leave.
|
|
7
7
|
|
|
8
|
-
If you read one line: **a verdict is entirely local and needs no account.** Nothing is sent anywhere until you run `reticle login` and `reticle link`. Without them there is nowhere for
|
|
8
|
+
If you read one line: **a verdict is entirely local and needs no account.** Nothing from your project is sent anywhere until you run `reticle login` and `reticle link`. Without them there is nowhere for it to go. (Anonymous usage counts, which carry nothing from your app, are separate and described in [Telemetry](telemetry.md).)
|
|
9
9
|
|
|
10
10
|
## On your machine
|
|
11
11
|
|
|
@@ -31,7 +31,7 @@ Network bodies are **not** captured by default. Page content that reaches a verd
|
|
|
31
31
|
|
|
32
32
|
## What leaves, only if you ask
|
|
33
33
|
|
|
34
|
-
`reticle login` signs this machine in. `reticle link` binds one repo to one cloud project. Until both have happened, `reticle push` has nothing to talk to and the sync path is a no-op. The code calls this the no-phone-home default.
|
|
34
|
+
`reticle login` signs this machine in. `reticle link` binds one repo to one cloud project. Until both have happened, or `RETICLE_API_KEY` is set in the environment, `reticle push` has nothing to talk to and the sync path is a no-op. The code calls this the no-phone-home default. A key in the environment is how CI syncs with no login and no link, and it goes to the hosted service unless `RETICLE_CLOUD_URL` names another host.
|
|
35
35
|
|
|
36
36
|
Once linked, you choose what syncs:
|
|
37
37
|
|
package/docs/what-is-reticle.mdx
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: What is Reticle?
|
|
3
3
|
sidebarTitle: What is Reticle
|
|
4
|
-
description: Reticle is a verification layer for AI coding agents. It runs a dev-only SDK inside your running web app so an agent can prove a change works, instead of guessing. What it is,
|
|
4
|
+
description: Reticle is a verification layer for AI coding agents. It runs a dev-only SDK inside your running web app so an agent can prove a change works, instead of guessing. What it is, where it fits beside other tools, and what it needs.
|
|
5
5
|
icon: circle-question
|
|
6
6
|
---
|
|
7
7
|
|
|
@@ -12,37 +12,33 @@ It exists because an agent that writes code cannot otherwise tell whether that c
|
|
|
12
12
|
## How it works, in three parts
|
|
13
13
|
|
|
14
14
|
1. **An SDK inside the app.** Dev-only, stripped from production builds, added by a build plugin. It instruments the DOM, network, routing, console, animations, and your state library.
|
|
15
|
-
2. **A local bridge.** The SDK connects over a WebSocket to a daemon on your machine. The daemon never
|
|
16
|
-
3. **An MCP server.** The daemon exposes the app to your agent as tools. `
|
|
15
|
+
2. **A local bridge.** The SDK connects over a WebSocket to a daemon on your machine, bound to `127.0.0.1`. The daemon never manages your dev server; it attaches to the app that is running. On first setup, `npx @reticlehq/server init` starts the dev server with your project's own dev script and opens the app.
|
|
16
|
+
3. **An MCP server.** The daemon exposes the app to your agent as tools. `reticle_look` to look, `reticle_act_and_wait` to act and prove in one call, `reticle_observe` to read what the app did, `reticle_assert` to check.
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
Your app's data stays on your machine: no application code, DOM, network traffic, state or console output leaves it. Anonymous usage counts are sent by default and can be [turned off](/telemetry).
|
|
19
19
|
|
|
20
|
-
##
|
|
20
|
+
## Where it fits
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
Reticle sits next to several familiar categories, and it works alongside each of them.
|
|
23
23
|
|
|
24
|
-
**
|
|
24
|
+
**Browser automation.** Playwright, Cypress and Selenium drive a browser from the outside. Reticle runs inside the page you already have open and answers "did the thing I just changed actually work?" Playwright remains the tool for sites you don't own and for a cross-browser matrix.
|
|
25
25
|
|
|
26
|
-
**
|
|
26
|
+
**Your test suite.** Unit and integration tests keep doing their job. Reticle covers the layer they cannot reach: behaviour that only exists when the whole application runs, which is often where a suite passes over a broken app.
|
|
27
27
|
|
|
28
|
-
**
|
|
28
|
+
**Visual testing.** Reticle can take screenshots, but its verdicts come from predicates over real events (a request with a status, a state path, a signal your code fired), so they do not break when a button moves four pixels. For pixel-level diffs, pair it with a visual testing tool.
|
|
29
29
|
|
|
30
|
-
**
|
|
30
|
+
**Security, accessibility, performance and SEO.** Reticle checks how your running app behaves in each: access control by role, calls that must never fire, focus and keyboard behaviour, request counts and long tasks, titles, links and redirects. It pairs with a vulnerability scanner, a full WCAG audit tool such as axe, and an SEO crawler. [Use cases](/use-cases) has what each check covers.
|
|
31
31
|
|
|
32
|
-
**
|
|
32
|
+
**Monitoring.** Reticle runs against environments you control (a dev server, a staging or preview deployment) and replays saved flows in CI. The SDK refuses to connect in a production build, so it complements an APM or error tracker rather than watching live traffic.
|
|
33
33
|
|
|
34
|
-
**
|
|
34
|
+
**Your agent.** Reticle has no model of its own for the verify loop. It gives the agent you already use a way to see the running app. Your agent, your model, your keys. (`explore` can drive a journey for you with a model inside the daemon; see [Autodrive](/autodrive).)
|
|
35
35
|
|
|
36
|
-
**
|
|
37
|
-
|
|
38
|
-
**Not a way to make a failing check pass.** A verdict of `unknown` means Reticle drove the app and could not tell. It is deliberately not rounded up to a pass, because a verification tool that flatters you is worse than none.
|
|
36
|
+
**The verdict.** A verdict of `unknown` means Reticle drove the app and could not tell. It is deliberately not rounded up to a pass.
|
|
39
37
|
|
|
40
38
|
## What Reticle needs
|
|
41
39
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
- **A web application you can run locally**, with its dev server. Reticle attaches; it does not start one.
|
|
45
|
-
- **A build step it can hook**, or two lines added by hand. Vite, Next.js, Remix and Astro are proven by gates in the repository. The SDK is framework agnostic and usually connects elsewhere, with reduced source mapping.
|
|
40
|
+
- **A web application you can run**, with a dev build. `init` starts its dev server for you.
|
|
41
|
+
- **A build step it can hook**, or two lines added by hand. [Frameworks](/frameworks) lists what is proven for each.
|
|
46
42
|
- **An agent that speaks MCP.** Claude Code, Cursor, Codex, Copilot, Windsurf, OpenCode and others.
|
|
47
43
|
- **Node 20 or newer.**
|
|
48
44
|
|
|
@@ -59,5 +55,6 @@ Side by side with the tools people ask about most:
|
|
|
59
55
|
## Where to go next
|
|
60
56
|
|
|
61
57
|
- [Quickstart](/quickstart) gets a real verdict out of your own app in about five minutes.
|
|
62
|
-
- [Why Reticle](/why-reticle) is the argument, with the evidence and
|
|
58
|
+
- [Why Reticle](/why-reticle) is the argument, with the evidence and what pairs well with it.
|
|
59
|
+
- [Use cases](/use-cases) covers what people verify with it.
|
|
63
60
|
- [For agents](/for-agents) is written for the agent rather than for you.
|
package/docs/why-reticle.mdx
CHANGED
|
@@ -63,37 +63,30 @@ Observing one real dashboard once, with no bug involved:
|
|
|
63
63
|
That last column is the one that matters more than the token counts. The other tools can _describe_ what a page looks like. Only Reticle can check whether the thing the app itself considers success actually happened.
|
|
64
64
|
|
|
65
65
|
<Card title="How every number was measured" icon="chart-column" href="/benchmarks">
|
|
66
|
-
The full method, the raw data, and the scenarios where
|
|
66
|
+
The full method, the raw data, and the scenarios where the tools tie.
|
|
67
67
|
</Card>
|
|
68
68
|
|
|
69
|
-
|
|
69
|
+
On raw cost per glance, Chrome DevTools MCP is cheaper: 758 tokens against 815. It catches fewer bugs, which is why the efficiency column favours Reticle.
|
|
70
70
|
|
|
71
|
-
|
|
71
|
+
## What the fix-rate test measured
|
|
72
72
|
|
|
73
|
-
-
|
|
74
|
-
- **Pixels.** Reticle reads the program, not the screen. If your bug is "the button is three pixels too far left", Reticle is the wrong instrument.
|
|
75
|
-
- **Chrome DevTools MCP is cheaper per look.** 758 tokens against our 815. It catches fewer bugs, so the efficiency works out in our favour, but on raw cost per glance it wins.
|
|
73
|
+
We ran a fix-loop ablation: agents fixed 8 bugs injected into a small fixture app, with Reticle and without it, on a strong model and on a weaker one. **It did not show a fix-rate improvement at either tier.** The strong model fixed 8 of 8 in both conditions and the weaker one 7 of 8 in both, and the Reticle arm made roughly five to six times the tool calls, because driving an app costs more than reading a few short files.
|
|
76
74
|
|
|
77
|
-
|
|
75
|
+
The test cannot settle the question in either direction, and the reasons are specific: the fixture is small enough to read whole, several injected bugs were labelled by a comment on the changed line, and "fixed" was checked by a string disappearing rather than by the app behaving. One case pointed each way. The weaker model without Reticle claimed a fix it never applied, and the live re-check caught that with Reticle; with Reticle, the same model over-edited a stylesheet the string check scored as a pass. The full log is in [`bench/fix-loop/results.md`](https://github.com/reticlehq/reticle/blob/main/bench/fix-loop/results.md).
|
|
78
76
|
|
|
79
|
-
|
|
77
|
+
A separate, smaller measurement of the source pointer alone found the same fix rate with and without it (6 of 6) and fewer tool calls with it (12 against 22). The pointer removed the search; it did not make the agent more capable.
|
|
80
78
|
|
|
81
|
-
|
|
79
|
+
That is what Reticle is designed to improve: not the agent, but the verdict it reports. A broken build reads as broken, a failure arrives with the evidence and the `file:line`, and "I couldn't tell" is reported as that. In the 88-bug head-to-head against a Playwright script, Reticle reported one false green where the script reported 29.
|
|
82
80
|
|
|
83
|
-
|
|
81
|
+
## When to use it
|
|
84
82
|
|
|
85
|
-
|
|
83
|
+
You own the app, an agent edits it, and you want the agent to prove its work rather than assure you of it. It needs a dev build it can instrument, and React (and any framework whose build plugin stamps source) adds source pointers on top. [Use cases](/use-cases) covers what people check with it beyond change verification, from access control to accessibility.
|
|
86
84
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
<Card title="A bad fit" icon="circle-xmark">
|
|
93
|
-
You're testing a site you don't control, you need pixel-level visual regression, or your app has
|
|
94
|
-
no dev build to instrument.
|
|
95
|
-
</Card>
|
|
96
|
-
</CardGroup>
|
|
85
|
+
## Pairs well with
|
|
86
|
+
|
|
87
|
+
- **Playwright** for driving sites you don't own and a cross-browser matrix. Reticle needs its dev-only SDK inside the app.
|
|
88
|
+
- **A visual testing tool** for pixel-level regression. Reticle reads the program, not the screen, so "the button is three pixels too far left" is a job for pixels.
|
|
89
|
+
- **axe** for full WCAG audits, and **a security scanner** for vulnerability discovery.
|
|
97
90
|
|
|
98
91
|
## The one-sentence version
|
|
99
92
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reticlehq/server",
|
|
3
|
-
"version": "3.
|
|
4
|
-
"description": "Reticle bridge
|
|
3
|
+
"version": "3.5.0",
|
|
4
|
+
"description": "Reticle CLI, bridge and MCP server: a coding agent drives your running web app from the inside and gets a verdict with evidence, for change verification, access control, accessibility, performance and SEO checks.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
7
7
|
"types": "./dist/index.d.ts",
|
|
@@ -19,24 +19,24 @@
|
|
|
19
19
|
}
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
22
|
-
"@modelcontextprotocol/sdk": "^1.30.
|
|
22
|
+
"@modelcontextprotocol/sdk": "^1.30.1",
|
|
23
23
|
"ws": "^8.21.3",
|
|
24
24
|
"zod": "^3.24.1",
|
|
25
25
|
"zod-to-json-schema": "^3.24.0",
|
|
26
|
-
"@reticlehq/
|
|
27
|
-
"@reticlehq/
|
|
28
|
-
"@reticlehq/
|
|
26
|
+
"@reticlehq/browser": "3.5.0",
|
|
27
|
+
"@reticlehq/engine": "3.5.0",
|
|
28
|
+
"@reticlehq/core": "3.5.0",
|
|
29
29
|
"open-verification": "1.0.0",
|
|
30
|
-
"@reticlehq/
|
|
30
|
+
"@reticlehq/init": "3.5.0"
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
33
33
|
"@types/pngjs": "^6.0.5",
|
|
34
34
|
"@types/ws": "^8.5.13",
|
|
35
|
-
"yaml": "^2.9.
|
|
35
|
+
"yaml": "^2.9.1"
|
|
36
36
|
},
|
|
37
37
|
"optionalDependencies": {
|
|
38
38
|
"pixelmatch": "^7.2.0",
|
|
39
|
-
"playwright": "^1.
|
|
39
|
+
"playwright": "^1.63.0",
|
|
40
40
|
"pngjs": "^7.0.0"
|
|
41
41
|
},
|
|
42
42
|
"license": "FSL-1.1-ALv2",
|
|
@@ -60,6 +60,7 @@
|
|
|
60
60
|
"mcp",
|
|
61
61
|
"ai-agents",
|
|
62
62
|
"testing",
|
|
63
|
+
"verification",
|
|
63
64
|
"cli"
|
|
64
65
|
],
|
|
65
66
|
"reticle": {
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Bug-capsule minimization — first cut: prefix-trim. A recorded flow that fails is often mostly setup;
|
|
3
|
-
* the capsule should be the SMALLEST flow that still reproduces the failure, so a regression flow stays
|
|
4
|
-
* fast and legible. Prefix-trim drops leading steps while the failure persists — the cheap, deterministic
|
|
5
|
-
* v1 (delta-debugging over arbitrary subsets is a later refinement). The `stillFails` predicate is
|
|
6
|
-
* injected (a real replay), so this stays pure and unit-testable.
|
|
7
|
-
*/
|
|
8
|
-
/**
|
|
9
|
-
* Return the shortest trailing sub-flow that still fails, by removing leading steps one at a time while
|
|
10
|
-
* `stillFails` holds. Never trims below one step. Deterministic given a deterministic predicate.
|
|
11
|
-
*/
|
|
12
|
-
export declare function prefixTrim<T>(steps: readonly T[], stillFails: (candidate: readonly T[]) => Promise<boolean>): Promise<T[]>;
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Bug-capsule minimization — first cut: prefix-trim. A recorded flow that fails is often mostly setup;
|
|
3
|
-
* the capsule should be the SMALLEST flow that still reproduces the failure, so a regression flow stays
|
|
4
|
-
* fast and legible. Prefix-trim drops leading steps while the failure persists — the cheap, deterministic
|
|
5
|
-
* v1 (delta-debugging over arbitrary subsets is a later refinement). The `stillFails` predicate is
|
|
6
|
-
* injected (a real replay), so this stays pure and unit-testable.
|
|
7
|
-
*/
|
|
8
|
-
/**
|
|
9
|
-
* Return the shortest trailing sub-flow that still fails, by removing leading steps one at a time while
|
|
10
|
-
* `stillFails` holds. Never trims below one step. Deterministic given a deterministic predicate.
|
|
11
|
-
*/
|
|
12
|
-
export async function prefixTrim(steps, stillFails) {
|
|
13
|
-
let kept = [...steps];
|
|
14
|
-
while (kept.length > 1) {
|
|
15
|
-
const trimmed = kept.slice(1);
|
|
16
|
-
if (await stillFails(trimmed))
|
|
17
|
-
kept = trimmed;
|
|
18
|
-
else
|
|
19
|
-
break;
|
|
20
|
-
}
|
|
21
|
-
return kept;
|
|
22
|
-
}
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import { PhenomenonType, type JournalAction, type ReticleEvent } from '@reticlehq/core';
|
|
2
|
-
/**
|
|
3
|
-
* A detected phenomenon — a named, evidence-backed anomaly over the journal. Deterministic matchers, no
|
|
4
|
-
* scanning by the agent: the layer names the fault, the agent acts on the name. `pre-hydration-click`
|
|
5
|
-
* lands once the hydration signal (a later item) exists; the three here need only journal data.
|
|
6
|
-
*/
|
|
7
|
-
interface Finding {
|
|
8
|
-
phenomenon: PhenomenonType;
|
|
9
|
-
evidence: Record<string, unknown>;
|
|
10
|
-
}
|
|
11
|
-
/** A request that started (NET_PENDING) but never completed (no NET_REQUEST with the same id). */
|
|
12
|
-
export declare function detectHungRequests(events: readonly ReticleEvent[]): Finding[];
|
|
13
|
-
/** A 5xx response observed while the page was hidden — an error the user never sees. */
|
|
14
|
-
export declare function detectHidden500(events: readonly ReticleEvent[]): Finding[];
|
|
15
|
-
/** An act that dispatched but attributed no events — the app did nothing. */
|
|
16
|
-
export declare function detectDeadClicks(actions: readonly JournalAction[]): Finding[];
|
|
17
|
-
/**
|
|
18
|
-
* A click that landed BEFORE React hydration attached handlers — a silent no-op. Detectable only in-app:
|
|
19
|
-
* the hydration-complete signal's time vs the click action's dispatch. No signal ⇒ can't tell (not a
|
|
20
|
-
* React app, or hydration never completed), so we don't guess.
|
|
21
|
-
*/
|
|
22
|
-
export declare function detectPreHydrationClicks(events: readonly ReticleEvent[], actions: readonly JournalAction[]): Finding[];
|
|
23
|
-
/** A React error boundary that caught and swallowed — surfaced on the signal channel by the adapter. */
|
|
24
|
-
export declare function detectSwallowedErrors(events: readonly ReticleEvent[]): Finding[];
|
|
25
|
-
/** Run every journal-only matcher over a session's events + actions. */
|
|
26
|
-
export declare function detectPhenomena(events: readonly ReticleEvent[], actions: readonly JournalAction[]): Finding[];
|
|
27
|
-
export {};
|
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
import { EventType, FlowStepTool, PhenomenonType, RETICLE_ERROR_BOUNDARY_SIGNAL, RETICLE_HYDRATION_SIGNAL, } from '@reticlehq/core';
|
|
2
|
-
/** ACT tools that represent a user-style interaction (a "click"), for dead-click detection. */
|
|
3
|
-
const CLICK_TOOLS = new Set([FlowStepTool.ACT, FlowStepTool.ACT_AND_WAIT]);
|
|
4
|
-
/** A request that started (NET_PENDING) but never completed (no NET_REQUEST with the same id). */
|
|
5
|
-
export function detectHungRequests(events) {
|
|
6
|
-
const completed = new Set();
|
|
7
|
-
for (const event of events) {
|
|
8
|
-
if (event.type === EventType.NET_REQUEST)
|
|
9
|
-
completed.add(event.data['id']);
|
|
10
|
-
}
|
|
11
|
-
const findings = [];
|
|
12
|
-
for (const event of events) {
|
|
13
|
-
if (event.type !== EventType.NET_PENDING)
|
|
14
|
-
continue;
|
|
15
|
-
if (completed.has(event.data['id']))
|
|
16
|
-
continue;
|
|
17
|
-
findings.push({
|
|
18
|
-
phenomenon: PhenomenonType.HUNG_REQUEST,
|
|
19
|
-
evidence: { id: event.data['id'], method: event.data['method'], url: event.data['url'] },
|
|
20
|
-
});
|
|
21
|
-
}
|
|
22
|
-
return findings;
|
|
23
|
-
}
|
|
24
|
-
/** A 5xx response observed while the page was hidden — an error the user never sees. */
|
|
25
|
-
export function detectHidden500(events) {
|
|
26
|
-
const findings = [];
|
|
27
|
-
let hidden = false;
|
|
28
|
-
for (const event of events) {
|
|
29
|
-
if (event.type === EventType.PAGE_HEALTH) {
|
|
30
|
-
const h = event.data['hidden'];
|
|
31
|
-
if ('boolean' === typeof h)
|
|
32
|
-
hidden = h;
|
|
33
|
-
continue;
|
|
34
|
-
}
|
|
35
|
-
if (event.type !== EventType.NET_REQUEST)
|
|
36
|
-
continue;
|
|
37
|
-
const status = event.data['status'];
|
|
38
|
-
if (hidden && 'number' === typeof status && status >= 500) {
|
|
39
|
-
findings.push({
|
|
40
|
-
phenomenon: PhenomenonType.HIDDEN_500,
|
|
41
|
-
evidence: { url: event.data['url'], status, method: event.data['method'] },
|
|
42
|
-
});
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
return findings;
|
|
46
|
-
}
|
|
47
|
-
/** An act that dispatched but attributed no events — the app did nothing. */
|
|
48
|
-
export function detectDeadClicks(actions) {
|
|
49
|
-
const findings = [];
|
|
50
|
-
for (const action of actions) {
|
|
51
|
-
if (!CLICK_TOOLS.has(action.tool))
|
|
52
|
-
continue;
|
|
53
|
-
if (action.seqRange !== undefined)
|
|
54
|
-
continue; // it caused something → not dead
|
|
55
|
-
findings.push({
|
|
56
|
-
phenomenon: PhenomenonType.DEAD_CLICK,
|
|
57
|
-
evidence: { actionId: action.actionId, tool: action.tool, args: action.args },
|
|
58
|
-
});
|
|
59
|
-
}
|
|
60
|
-
return findings;
|
|
61
|
-
}
|
|
62
|
-
/**
|
|
63
|
-
* A click that landed BEFORE React hydration attached handlers — a silent no-op. Detectable only in-app:
|
|
64
|
-
* the hydration-complete signal's time vs the click action's dispatch. No signal ⇒ can't tell (not a
|
|
65
|
-
* React app, or hydration never completed), so we don't guess.
|
|
66
|
-
*/
|
|
67
|
-
export function detectPreHydrationClicks(events, actions) {
|
|
68
|
-
const hydration = events.find((e) => e.type === EventType.SIGNAL && e.data['name'] === RETICLE_HYDRATION_SIGNAL);
|
|
69
|
-
if (hydration === undefined)
|
|
70
|
-
return [];
|
|
71
|
-
return actions
|
|
72
|
-
.filter((a) => CLICK_TOOLS.has(a.tool) && a.tRange.from < hydration.t)
|
|
73
|
-
.map((a) => ({
|
|
74
|
-
phenomenon: PhenomenonType.PRE_HYDRATION_CLICK,
|
|
75
|
-
evidence: { actionId: a.actionId, clickedAt: a.tRange.from, hydratedAt: hydration.t },
|
|
76
|
-
}));
|
|
77
|
-
}
|
|
78
|
-
/** A React error boundary that caught and swallowed — surfaced on the signal channel by the adapter. */
|
|
79
|
-
export function detectSwallowedErrors(events) {
|
|
80
|
-
const findings = [];
|
|
81
|
-
for (const event of events) {
|
|
82
|
-
if (event.type === EventType.SIGNAL && event.data['name'] === RETICLE_ERROR_BOUNDARY_SIGNAL) {
|
|
83
|
-
const detail = event.data['data'];
|
|
84
|
-
findings.push({
|
|
85
|
-
phenomenon: PhenomenonType.SWALLOWED_ERROR,
|
|
86
|
-
evidence: 'object' === typeof detail && detail !== null ? detail : {},
|
|
87
|
-
});
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
return findings;
|
|
91
|
-
}
|
|
92
|
-
/** Run every journal-only matcher over a session's events + actions. */
|
|
93
|
-
export function detectPhenomena(events, actions) {
|
|
94
|
-
return [
|
|
95
|
-
...detectHungRequests(events),
|
|
96
|
-
...detectHidden500(events),
|
|
97
|
-
...detectDeadClicks(actions),
|
|
98
|
-
...detectPreHydrationClicks(events, actions),
|
|
99
|
-
...detectSwallowedErrors(events),
|
|
100
|
-
];
|
|
101
|
-
}
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import { type FlowFile, type FlowReplayResult } from '@reticlehq/core';
|
|
2
|
-
/**
|
|
3
|
-
* Render a HUMAN-facing confidence report for a replayed flow — the artifact a developer reads to
|
|
4
|
-
* trust the test without re-running it. It answers the three confidence questions in one page:
|
|
5
|
-
* WHY — the declared business intent (+ whether it is actually asserted).
|
|
6
|
-
* WHAT — the journey: a mermaid flow diagram + a per-step page → action → consequence table.
|
|
7
|
-
* PROOF — the verdict (pass/drift/fail), the observed evidence (signals/network), and the
|
|
8
|
-
* measured cost (deterministic replay tokens vs an LLM re-drive).
|
|
9
|
-
*
|
|
10
|
-
* Pure: no IO, no clock. Markdown out (mermaid is the one place mermaid earns its tokens — a human
|
|
11
|
-
* renders it). Token figures are passed in (measured by the caller), never faked.
|
|
12
|
-
*/
|
|
13
|
-
interface FlowReportInput {
|
|
14
|
-
flow: FlowFile;
|
|
15
|
-
replay: FlowReplayResult;
|
|
16
|
-
/** Measured deterministic replay cost (o200k proxy), for the "PROOF / cost" line. Optional. */
|
|
17
|
-
replayTokens?: number;
|
|
18
|
-
/** Measured per-run LLM re-drive cost of a step-driving tool, for the ratio. Optional. */
|
|
19
|
-
competitorTokens?: number;
|
|
20
|
-
}
|
|
21
|
-
export declare function buildFlowReport(input: FlowReportInput): string;
|
|
22
|
-
export {};
|
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
import { ReplayStatus, } from '@reticlehq/core';
|
|
2
|
-
import { classifyFlowAssertions } from './flow-classify.js';
|
|
3
|
-
import { SUCCESS_STEP_TOOL } from './flow-success.js';
|
|
4
|
-
const VERDICT_GLYPH = {
|
|
5
|
-
[ReplayStatus.OK]: '✅ pass',
|
|
6
|
-
[ReplayStatus.DRIFT]: '⚠️ drift',
|
|
7
|
-
[ReplayStatus.ERROR]: '❌ fail',
|
|
8
|
-
};
|
|
9
|
-
/** A mermaid-safe node id (mermaid ids must be alnum/underscore). */
|
|
10
|
-
function nodeId(index) {
|
|
11
|
-
return `s${index}`;
|
|
12
|
-
}
|
|
13
|
-
/** Escape a label for a mermaid `["..."]` node (quotes/newlines break the parser). */
|
|
14
|
-
function mermaidLabel(text) {
|
|
15
|
-
return text.replace(/"/g, "'").replace(/\n/g, ' ').slice(0, 80);
|
|
16
|
-
}
|
|
17
|
-
/** A short action verb for a step row ("act" is the tool; the anchor carries the target). */
|
|
18
|
-
function stepAction(step) {
|
|
19
|
-
return step.tool === SUCCESS_STEP_TOOL ? 'assert outcome' : step.anchor;
|
|
20
|
-
}
|
|
21
|
-
/** The mermaid flowchart of the journey: one node per step, page-labelled, consequence on the edge. */
|
|
22
|
-
function mermaidDiagram(steps) {
|
|
23
|
-
const lines = ['```mermaid', 'flowchart TD'];
|
|
24
|
-
steps.forEach((step, i) => {
|
|
25
|
-
const page = step.page !== undefined ? `${step.page} — ` : '';
|
|
26
|
-
const glyph = step.ok ? '' : ' ✗';
|
|
27
|
-
lines.push(` ${nodeId(i)}["${mermaidLabel(`${page}${stepAction(step)}${glyph}`)}"]`);
|
|
28
|
-
if (i > 0) {
|
|
29
|
-
const edge = steps[i - 1]?.consequence;
|
|
30
|
-
const label = edge !== undefined ? `|"${mermaidLabel(edge)}"|` : '';
|
|
31
|
-
lines.push(` ${nodeId(i - 1)} -->${label} ${nodeId(i)}`);
|
|
32
|
-
}
|
|
33
|
-
});
|
|
34
|
-
lines.push('```');
|
|
35
|
-
return lines.join('\n');
|
|
36
|
-
}
|
|
37
|
-
/** The per-step journey table (page → action → consequence → result). */
|
|
38
|
-
function journeyTable(steps) {
|
|
39
|
-
const rows = [
|
|
40
|
-
'| # | page | action | consequence | result |',
|
|
41
|
-
'|---|------|--------|-------------|--------|',
|
|
42
|
-
];
|
|
43
|
-
for (const step of steps) {
|
|
44
|
-
const page = step.page ?? '—';
|
|
45
|
-
const consequence = step.consequence ?? '—';
|
|
46
|
-
const result = step.ok
|
|
47
|
-
? 'ok'
|
|
48
|
-
: step.drift !== undefined
|
|
49
|
-
? `drift (${step.drift.anchor})`
|
|
50
|
-
: 'fail';
|
|
51
|
-
rows.push(`| ${step.step} | ${page} | ${stepAction(step)} | ${consequence} | ${result} |`);
|
|
52
|
-
}
|
|
53
|
-
return rows.join('\n');
|
|
54
|
-
}
|
|
55
|
-
/** Distinct observed consequences across the journey — the evidence the verdict rests on. */
|
|
56
|
-
function evidenceList(steps) {
|
|
57
|
-
const seen = new Set();
|
|
58
|
-
for (const step of steps) {
|
|
59
|
-
if (step.consequence !== undefined) {
|
|
60
|
-
for (const part of step.consequence.split(';'))
|
|
61
|
-
seen.add(part.trim());
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
if (0 === seen.size)
|
|
65
|
-
return '_No observable consequence captured._';
|
|
66
|
-
return [...seen].map((e) => `- ${e}`).join('\n');
|
|
67
|
-
}
|
|
68
|
-
export function buildFlowReport(input) {
|
|
69
|
-
const { flow, replay, replayTokens, competitorTokens } = input;
|
|
70
|
-
const grade = classifyFlowAssertions(flow);
|
|
71
|
-
const verdict = VERDICT_GLYPH[replay.status] ?? replay.status;
|
|
72
|
-
const intent = flow.intent ?? '_(no intent declared)_';
|
|
73
|
-
const intentLine = flow.intent !== undefined
|
|
74
|
-
? grade.intentVerified
|
|
75
|
-
? `**Intent:** ${flow.intent} — **verified** (an observable outcome is asserted).`
|
|
76
|
-
: `**Intent:** ${flow.intent} — ⚠️ declared but NOT asserted (add a consequence oracle).`
|
|
77
|
-
: `**Intent:** ${intent}`;
|
|
78
|
-
const out = [];
|
|
79
|
-
out.push(`# Flow report — \`${flow.name}\``);
|
|
80
|
-
out.push('');
|
|
81
|
-
out.push(intentLine);
|
|
82
|
-
out.push(`**Verdict:** ${verdict} · **steps:** ${replay.steps.length}`);
|
|
83
|
-
if (replayTokens !== undefined) {
|
|
84
|
-
const ratio = competitorTokens !== undefined && replayTokens > 0
|
|
85
|
-
? ` · **${Math.round(competitorTokens / replayTokens)}×** cheaper than an LLM re-drive`
|
|
86
|
-
: '';
|
|
87
|
-
out.push(`**Cost:** ${replayTokens} tokens (deterministic replay, no LLM)${ratio}`);
|
|
88
|
-
}
|
|
89
|
-
if (replay.decision !== undefined) {
|
|
90
|
-
out.push('');
|
|
91
|
-
out.push(`> **Next action:** ${replay.decision.nextAction}`);
|
|
92
|
-
if (replay.decision.whereInSource !== undefined) {
|
|
93
|
-
out.push(`> **Where:** \`${replay.decision.whereInSource}\``);
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
out.push('');
|
|
97
|
-
out.push('## Journey');
|
|
98
|
-
out.push(mermaidDiagram(replay.steps));
|
|
99
|
-
out.push('');
|
|
100
|
-
out.push(journeyTable(replay.steps));
|
|
101
|
-
out.push('');
|
|
102
|
-
out.push('## Evidence');
|
|
103
|
-
out.push(evidenceList(replay.steps));
|
|
104
|
-
out.push('');
|
|
105
|
-
return out.join('\n');
|
|
106
|
-
}
|