@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
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
# Reticle Integration Patterns
|
|
2
|
+
|
|
3
|
+
The basics in [Getting Started](getting-started.md) work with **zero app changes**. This doc is the _recommended_ shape for a real codebase: a minimal production footprint, a signal layer that can't silently drift, and an adoption path that starts paying off on day one — no rewrite required.
|
|
4
|
+
|
|
5
|
+
- [1 — Start here: reuse what you already have](#1--start-here-reuse-what-you-already-have)
|
|
6
|
+
- [2 — Inject the emitter (zero prod bundle)](#2--inject-the-emitter-zero-prod-bundle)
|
|
7
|
+
- [3 — Emit signals from the store layer, not N call sites](#3--emit-signals-from-the-store-layer-not-n-call-sites)
|
|
8
|
+
- [4 — Self-registering domains (`registerReticleDomain`)](#4--self-registering-domains-registerreticledomain)
|
|
9
|
+
- [5 — Keep the signal layer from rotting (`@reticlehq/eslint-plugin`)](#5--keep-the-signal-layer-from-rotting-reticleeslint-plugin)
|
|
10
|
+
- [6 — Limitation: un-scriptable tabs → `reticle drive`](#6--limitation-un-scriptable-tabs--reticle-drive)
|
|
11
|
+
- [Checklist](#checklist)
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## 1 — Start here: reuse what you already have
|
|
16
|
+
|
|
17
|
+
Adoption is **free → cheap → targeted**. You don't instrument everything; you reuse what you have, then add the handful of facts the DOM can't express.
|
|
18
|
+
|
|
19
|
+
**1. Reuse your existing `data-testid` (free).** If you already test with Playwright or Cypress, your testids work in Reticle unchanged — `reticle_query({ by: 'testid', value: 'checkout' })` matches them exactly. No new markup, no new code.
|
|
20
|
+
|
|
21
|
+
**2. Advertise the surface from your existing constants (cheap).** You already keep a `TestIds` constant object for your E2E suite — pass it straight in. Now `reticle_capabilities()` tells a fresh agent your whole surface without reading source.
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
import { registerCapabilities } from '@reticlehq/browser';
|
|
25
|
+
import { TestIds } from '../e2e/test-ids'; // the same constants your Playwright suite uses
|
|
26
|
+
|
|
27
|
+
registerCapabilities({
|
|
28
|
+
testids: Object.values(TestIds),
|
|
29
|
+
signals: ['order:saved'],
|
|
30
|
+
stores: ['cart'],
|
|
31
|
+
});
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
**3. Add signals only at the ~20 commit points that matter (targeted).** Emit `reticle.signal(name, data)` at the moments the DOM can't show — a save committed, a webhook arrived, an edit applied, an async generation finished. You instrument the off-DOM facts you'd otherwise eyeball, not every line.
|
|
35
|
+
|
|
36
|
+
```ts
|
|
37
|
+
import { reticle } from '@reticlehq/browser';
|
|
38
|
+
onSaved(() => reticle.signal('order:saved', { id, total }));
|
|
39
|
+
// agent: reticle_assert({ predicate: { kind: 'signal', name: 'order:saved', dataMatches: { id: '*' } } })
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
That's day-one usefulness with no rewrite. The rest of this doc is how to do steps 2–3 _well_ so the signal layer stays honest as the app grows.
|
|
43
|
+
|
|
44
|
+
## 2 — Inject the emitter (zero prod bundle)
|
|
45
|
+
|
|
46
|
+
The #1 objection: _"I don't want a test tool in my production bundle."_ The answer: components never import `@reticlehq/browser`. They depend on a tiny structural interface, `ReticleEmitter` (`{ signal, state }`), and the real emitter is injected once at the top. `createReticleEmitter()` returns an emitter that proxies to the connected `reticle` singleton and is a **safe no-op** until `reticle.connect()` runs — so nothing breaks in production or before connect, and **`@reticlehq/browser` stays out of the prod bundle.**
|
|
47
|
+
|
|
48
|
+
```ts
|
|
49
|
+
// app/emit.ts — the one place that touches @reticlehq/browser
|
|
50
|
+
import { createReticleEmitter } from '@reticlehq/browser';
|
|
51
|
+
export const emit = createReticleEmitter(); // no-op until reticle.connect()
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
// any component — depends on the interface, not the SDK
|
|
56
|
+
import { emit } from '../emit';
|
|
57
|
+
function onSaved(id: string, total: number) {
|
|
58
|
+
emit.signal('order:saved', { id, total });
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
The emitter re-checks the connection on every call, so you can create it at module load — before `reticle.connect()` — and it starts forwarding the moment Reticle connects. (See [getting-started Step 2](getting-started.md#step-2--embed-the-sdk-in-your-app) for where `reticle.connect()` goes.) This is the single highest-leverage decision; everything below assumes it.
|
|
63
|
+
|
|
64
|
+
## 3 — Emit signals from the store layer, not N call sites
|
|
65
|
+
|
|
66
|
+
The smell: every store mutation hand-emits a signal right after it, and over dozens of call sites the two **drift** — a new mutation path forgets the emit and the contract silently breaks. Drive the signal from where the state actually changes instead.
|
|
67
|
+
|
|
68
|
+
**Pattern A — store middleware (sketch).** One audited map from action → signal lives next to the store, so _state changed ⇒ signal fired_ is structural, not a thing each call site remembers.
|
|
69
|
+
|
|
70
|
+
```ts
|
|
71
|
+
// store-with-reticle.ts — sketch: one audited transition map, not N call sites
|
|
72
|
+
import { emit } from './emit';
|
|
73
|
+
|
|
74
|
+
const signalFor: Record<string, (next: State) => readonly [string, Record<string, unknown>]> = {
|
|
75
|
+
reorderSections: (s) => ['section:reordered', { order: s.order }],
|
|
76
|
+
addSection: (s) => ['section:added', { count: s.sections.length }],
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
function dispatch(action: keyof typeof signalFor, run: () => State): void {
|
|
80
|
+
const next = run();
|
|
81
|
+
const make = signalFor[action];
|
|
82
|
+
if (make !== undefined) emit.signal(...make(next));
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
**Pattern B — `commitAndSignal` (lighter).** When you don't want a middleware, pair the mutation and its signal in one call that can't drift. It runs `mutate()`, emits the signal exactly once, and returns the mutation's value.
|
|
87
|
+
|
|
88
|
+
```ts
|
|
89
|
+
import { commitAndSignal } from '@reticlehq/browser';
|
|
90
|
+
import { emit } from '../emit';
|
|
91
|
+
|
|
92
|
+
const next = commitAndSignal(
|
|
93
|
+
emit,
|
|
94
|
+
() => store.reorderSections(fromId, toId),
|
|
95
|
+
'section:reordered',
|
|
96
|
+
deriveOrder(store.getState()),
|
|
97
|
+
);
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
If `mutate` throws, the mutation never happened — so **the signal is not emitted and the error propagates** unchanged.
|
|
101
|
+
|
|
102
|
+
> **The documented exception:** genuinely view-level signals — render or async completions like `diff:shown` or `caption:generated`, which aren't store state — legitimately stay in your components. Only commit-point signals belong in the store layer.
|
|
103
|
+
|
|
104
|
+
Pair this with store registration so the agent can _read_ state instead of you emitting a signal per fact: `registerStore('workspace', useWorkspace)`, then `reticle_state({ store: 'workspace' })`.
|
|
105
|
+
|
|
106
|
+
## 4 — Self-registering domains (`registerReticleDomain`)
|
|
107
|
+
|
|
108
|
+
Rather than maintaining one central flat-map of the whole testable surface (and remembering to wire each new area into it), co-locate one `reticle.ts` per domain that exports its `{ testids, signals, stores }` and self-registers. The capability registry assembles itself from every domain — later calls accumulate as a union, with no duplicates.
|
|
109
|
+
|
|
110
|
+
```ts
|
|
111
|
+
// features/sections/reticle.ts — co-locate a domain's testids + signals in one module
|
|
112
|
+
import { registerReticleDomain } from '@reticlehq/browser';
|
|
113
|
+
|
|
114
|
+
export const SectionTestIds = { list: 'section-list', add: 'section-add' } as const;
|
|
115
|
+
export const SectionSignals = { reordered: 'section:reordered' } as const;
|
|
116
|
+
|
|
117
|
+
registerReticleDomain({
|
|
118
|
+
testids: Object.values(SectionTestIds),
|
|
119
|
+
signals: Object.values(SectionSignals),
|
|
120
|
+
stores: ['workspace'],
|
|
121
|
+
});
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
```ts
|
|
125
|
+
// features/search/reticle.ts
|
|
126
|
+
import { registerReticleDomain } from '@reticlehq/browser';
|
|
127
|
+
registerReticleDomain({ testids: ['search-input'], signals: ['search:ran'] });
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Importing both modules in dev makes `reticle_capabilities()` return the merged surface (`testids: ['section-list', 'section-add', 'search-input']`, `signals: ['section:reordered', 'search:ran']`, `stores: ['workspace']`). `registerReticleDomain` is a thin convenience over `registerCapabilities` — same merge-idempotent, HMR-safe semantics — so it composes with §1's "use your existing constants." (Named flows stay an explicit `registerCapabilities({ flows })` concern — their last-writer-wins semantics don't fit "accumulate from many domains.")
|
|
131
|
+
|
|
132
|
+
## 5 — Keep the signal layer from rotting (`@reticlehq/eslint-plugin`)
|
|
133
|
+
|
|
134
|
+
A signal layer silently rots: someone adds a mutation path and forgets the signal, and the agent's contract breaks with no error. The lint rule catches it at the only moment that's cheap — review.
|
|
135
|
+
|
|
136
|
+
The rule **`reticle/require-signal-on-mutation`** flags a function that calls a configured store mutator but emits no signal in the same function. It is a **safe no-op until you tell it which calls mutate state** (`mutators`) and which call emits a signal (`signalCallee`, default `signal` / `reticleSignal`):
|
|
137
|
+
|
|
138
|
+
```js
|
|
139
|
+
// eslint.config.js (flat config)
|
|
140
|
+
import reticle from '@reticlehq/eslint-plugin';
|
|
141
|
+
|
|
142
|
+
export default [
|
|
143
|
+
{
|
|
144
|
+
plugins: { reticle },
|
|
145
|
+
rules: {
|
|
146
|
+
'reticle/require-signal-on-mutation': [
|
|
147
|
+
'warn',
|
|
148
|
+
{ mutators: ['set', 'reorderSections', 'addSection'], signalCallee: 'signal' },
|
|
149
|
+
],
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
];
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
Or turn it on with the shipped preset (warns, with empty no-op defaults you then configure): `plugin.configs.recommended`. A function that calls a mutator and a signal together passes; a mutator with no signal reports `store mutation without a mapped Reticle signal`. The documented view-level exceptions from §3 simply don't list those view callees as `mutators`, so they never fire.
|
|
156
|
+
|
|
157
|
+
## 6 — Limitation: un-scriptable tabs → `reticle drive`
|
|
158
|
+
|
|
159
|
+
Reticle observes and drives a tab through the in-page SDK plus (optionally) CDP. It **cannot bring to front or recover a browser tab the OS won't let it script** — e.g. a backgrounded tab, or a non-default browser (Dia, etc.) reporting `hidden:true` / `throttled:true`.
|
|
160
|
+
|
|
161
|
+
When that happens, `reticle_sessions` and every act/assert result carry a `session.recommendation` saying so. The escape hatch is **`reticle drive <url>`** (add `--headed` to watch) — Reticle launches and owns a guaranteed-scriptable browser. See [usage §18](usage.md#18-real-input-mode--native-hover--drag) for the full note.
|
|
162
|
+
|
|
163
|
+
## Checklist
|
|
164
|
+
|
|
165
|
+
- [ ] One `app/emit.ts` is the **only** module importing `@reticlehq/browser`; components import the emitter.
|
|
166
|
+
- [ ] `reticle.connect()` is dev-gated; the prod bundle has no `@reticlehq/browser`.
|
|
167
|
+
- [ ] Signals fire from the store layer (middleware or `commitAndSignal`); view-level exceptions are explicit.
|
|
168
|
+
- [ ] Each domain self-registers via `registerReticleDomain`; `reticle_capabilities()` returns the full surface.
|
|
169
|
+
- [ ] Existing Playwright/Cypress testids are reused, not duplicated.
|
|
170
|
+
- [ ] `reticle/require-signal-on-mutation` is enabled with your `mutators` + `signalCallee`.
|
|
171
|
+
- [ ] The team knows `reticle drive <url>` for un-scriptable tabs.
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# Test unpublished Reticle changes in a real app (local registry)
|
|
2
|
+
|
|
3
|
+
> **For normal use, Reticle is on public npm** — just `npm i -D @reticlehq/react @reticlehq/vite-plugin` (see [Getting Started](getting-started.md)). You only need this guide to test **local, unpublished changes** to the Reticle packages in a real external app before they ship.
|
|
4
|
+
|
|
5
|
+
Because the `@reticlehq/*` packages depend on each other via the workspace protocol, plain `npm pack` tarballs don't resolve cleanly. The reliable way to exercise your in-progress changes in a real app is a tiny **local registry** (Verdaccio) — the same path CI uses to validate a publish.
|
|
6
|
+
|
|
7
|
+
## 1. Publish @reticlehq/\* to a local registry
|
|
8
|
+
|
|
9
|
+
From the Reticle repo:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
bash scripts/local-registry.sh
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
This starts a **fresh** Verdaccio on `http://localhost:4873`, creates a user/token, and publishes all `@reticlehq/*` packages there at the current workspace version:
|
|
16
|
+
|
|
17
|
+
| Package | What you install it for |
|
|
18
|
+
| --- | --- |
|
|
19
|
+
| **`@reticlehq/react`** | **install this** — the browser SDK kit you embed (re-exports the browser sensor, so one install gives both `reticle` and `install`) |
|
|
20
|
+
| `@reticlehq/vite-plugin` | dev-only source mapping + `connect()` injection (Vite) |
|
|
21
|
+
| `@reticlehq/next` | Next.js build wrapper (`withReticle`) |
|
|
22
|
+
| `@reticlehq/server` | the bridge + MCP server (your agent runs it, `npx @reticlehq/server mcp`) |
|
|
23
|
+
| `@reticlehq/babel-plugin` | React 19 source stamping (Babel) |
|
|
24
|
+
| `@reticlehq/test` | write declarative, signal-bound specs (`reticleTest`) |
|
|
25
|
+
| `@reticlehq/eslint-plugin` | the `require-signal-on-mutation` lint rule |
|
|
26
|
+
| `@reticlehq/core` | shared wire contract (pulled in automatically) |
|
|
27
|
+
|
|
28
|
+
For a browser app, install `@reticlehq/react` plus the build plugin for your framework (`@reticlehq/vite-plugin` or `@reticlehq/next`); `@reticlehq/server` is what your agent runs. (Verified: an external `npm i @reticlehq/react` resolves its graph, including `@reticlehq/core`, and imports correctly.) Leave the registry running.
|
|
29
|
+
|
|
30
|
+
> Note: pre-2.0 docs used a single `@reticlehq/core` umbrella package that re-exported everything; it's been split into the audience-scoped packages above.
|
|
31
|
+
|
|
32
|
+
## 2. Point your app at the local registry
|
|
33
|
+
|
|
34
|
+
In your app's project root, add an `.npmrc` (scopes only `@reticle` to the local registry; everything else still comes from npm):
|
|
35
|
+
|
|
36
|
+
```ini
|
|
37
|
+
@reticle:registry=http://localhost:4873/
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## 3. Install + wire it up
|
|
41
|
+
|
|
42
|
+
Install the SDK kit plus the Vite build plugin (source mapping + `connect()` injection):
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
npm i -D @reticlehq/react @reticlehq/vite-plugin
|
|
46
|
+
# Next.js instead of Vite? npm i -D @reticlehq/react @reticlehq/next
|
|
47
|
+
# optional: npm i -D @reticlehq/eslint-plugin # require-signal-on-mutation lint rule
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Then follow [Getting Started](getting-started.md): embed `reticle.connect()` (dev only) from `@reticlehq/react`, add the MCP server to your agent, and (React) `install()` the adapter from `@reticlehq/react`. For the fastest agent loop, also do [Step 6 — make your app agent-legible](getting-started.md) (testids, `reticle.signal`, `registerStore`, `registerCapabilities`) and the [integration patterns](integration-patterns.md) (`createReticleEmitter` for zero prod-bundle cost).
|
|
51
|
+
|
|
52
|
+
> **Upgrading.** The packages are currently **1.2.0**; new tools land as minor bumps. `scripts/local-registry.sh` resets Verdaccio and republishes the current version, so pull the latest in your app explicitly — `npm install @reticlehq/react@latest`:
|
|
53
|
+
>
|
|
54
|
+
> ```bash
|
|
55
|
+
> npm i -D @reticlehq/react@latest @reticlehq/vite-plugin@latest @reticlehq/eslint-plugin@latest
|
|
56
|
+
> ```
|
|
57
|
+
|
|
58
|
+
**Run the MCP server** from the local registry too — `npx @reticlehq/server` _is_ the server:
|
|
59
|
+
|
|
60
|
+
```jsonc
|
|
61
|
+
// .mcp.json — point npx at the local registry so it fetches @reticlehq/server from Verdaccio
|
|
62
|
+
{
|
|
63
|
+
"mcpServers": {
|
|
64
|
+
"reticle": {
|
|
65
|
+
"command": "npx",
|
|
66
|
+
"args": ["--registry", "http://localhost:4873/", "@reticlehq/server", "mcp"],
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Next.js specifics (verified on Next 15 / React 19)
|
|
73
|
+
|
|
74
|
+
`next.config.mjs`:
|
|
75
|
+
|
|
76
|
+
```js
|
|
77
|
+
import reticleNext from '@reticlehq/next';
|
|
78
|
+
/** @type {import('next').NextConfig} */
|
|
79
|
+
const nextConfig = {};
|
|
80
|
+
export default reticleNext.withReticle(nextConfig); // dev-only; keeps SWC; adds file:line mapping
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Mount the SDK from a dev-only client component (see the Next.js section in [Getting Started](getting-started.md)).
|
|
84
|
+
|
|
85
|
+
## Real input for hover/drag (optional)
|
|
86
|
+
|
|
87
|
+
Synthetic events can't trigger native `onMouseEnter`/pointer state (hover menus, tooltips, pointer drag). Enable **real input** so the server drives genuine pointer input and `reticle_act` reports `inputMode:"real"`:
|
|
88
|
+
|
|
89
|
+
- **Easiest — `reticle drive`:** Reticle launches its own scriptable, headless-capable browser at your app URL (no flags to juggle):
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
npx --registry http://localhost:4873/ @reticlehq/server drive http://localhost:4310 # add --headed to watch
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
- **Or attach to your own browser:** launch it with `--remote-debugging-port=9222`, then point the MCP server at it via `env`:
|
|
96
|
+
|
|
97
|
+
```jsonc
|
|
98
|
+
// .mcp.json
|
|
99
|
+
{
|
|
100
|
+
"mcpServers": {
|
|
101
|
+
"reticle": {
|
|
102
|
+
"command": "npx",
|
|
103
|
+
"args": ["--registry", "http://localhost:4873/", "@reticlehq/server", "mcp"],
|
|
104
|
+
"env": { "RETICLE_CDP_URL": "http://localhost:9222" },
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
With neither set, Reticle stays synthetic (zero extra deps) and says so via `inputMode`. See [usage §18](usage.md#18-real-input-mode--native-hover--drag).
|
|
111
|
+
|
|
112
|
+
## Write replayable specs + git-checked flows
|
|
113
|
+
|
|
114
|
+
- **Specs:** with `@reticlehq/test`, turn checks into `reticleTest("…", async t => { await t.act(...); await t.expectSignal(...) })` — signal/testid-bound, `reticle_clock` for determinism, `t.expectInputModeReal()` to skip-with-reason when real input isn't active. Run them headless via `reticle drive` (the same path CI uses).
|
|
115
|
+
- **Flows:** record a flow once and Reticle writes it to a git-checked `.reticle/flows/<name>.json` (anchored on testid/signal); `reticle_flow_replay` re-resolves anchors at run time and reports **legible drift** with a nearest-match; `reticle_flow_heal` proposes/applies the rebind. A fresh agent reads `.reticle/contract.json` to learn your testable surface without grepping source.
|
|
116
|
+
|
|
117
|
+
## When you're ready for real npm
|
|
118
|
+
|
|
119
|
+
The same packages publish to public npm unchanged — `pnpm -r publish --access public` after `npm login`. The Verdaccio run above is a faithful rehearsal of that.
|
|
120
|
+
|
|
121
|
+
## Cleanup
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
pkill -f verdaccio # stop the local registry
|
|
125
|
+
# remove the line from your app's .npmrc when you switch to published packages
|
|
126
|
+
```
|
package/docs/mint.json
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://mintlify.com/schema.json",
|
|
3
|
+
"name": "Reticle",
|
|
4
|
+
"logo": {
|
|
5
|
+
"light": "/logo/light.svg",
|
|
6
|
+
"dark": "/logo/dark.svg"
|
|
7
|
+
},
|
|
8
|
+
"favicon": "/favicon.svg",
|
|
9
|
+
"colors": {
|
|
10
|
+
"primary": "#8b7bff",
|
|
11
|
+
"light": "#a99bff",
|
|
12
|
+
"dark": "#6b5bdf",
|
|
13
|
+
"background": {
|
|
14
|
+
"dark": "#0b0d12"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"topbarLinks": [
|
|
18
|
+
{
|
|
19
|
+
"name": "GitHub",
|
|
20
|
+
"url": "https://github.com/reticlehq/reticle"
|
|
21
|
+
}
|
|
22
|
+
],
|
|
23
|
+
"topbarCtaButton": {
|
|
24
|
+
"name": "npm",
|
|
25
|
+
"url": "https://www.npmjs.com/package/@reticlehq/react"
|
|
26
|
+
},
|
|
27
|
+
"navigation": [
|
|
28
|
+
{
|
|
29
|
+
"group": "Get started",
|
|
30
|
+
"pages": ["getting-started", "local-registry"]
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"group": "Integrate",
|
|
34
|
+
"pages": [
|
|
35
|
+
"platform-integration",
|
|
36
|
+
"desktop-apps",
|
|
37
|
+
"integration-patterns",
|
|
38
|
+
"flows",
|
|
39
|
+
"testing",
|
|
40
|
+
"multi-agent-testing",
|
|
41
|
+
"human-control",
|
|
42
|
+
"deploy-checks"
|
|
43
|
+
]
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
"group": "Concepts",
|
|
47
|
+
"pages": ["architecture", "benchmarks", "token-efficiency"]
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
"group": "Reference",
|
|
51
|
+
"pages": ["usage", "agent-cheatsheet"]
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
"group": "Enterprise",
|
|
55
|
+
"pages": ["enterprise"]
|
|
56
|
+
}
|
|
57
|
+
],
|
|
58
|
+
"footerSocials": {
|
|
59
|
+
"github": "https://github.com/reticlehq/reticle"
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# Multi-agent & multi-project testing
|
|
2
|
+
|
|
3
|
+
Reticle is built for the messy real world: several apps running at once, ports that shift between runs, and many agents driving different flows of the same app in parallel — without each one spinning up its own Chromium. This page explains how that works and how to use it.
|
|
4
|
+
|
|
5
|
+
## The mental model
|
|
6
|
+
|
|
7
|
+
- **One daemon per machine.** `reticle mcp` discovers a running daemon (via `~/.reticle`) or starts one. A crashed daemon's stale pidfile is reclaimed automatically, so you never chase "port already in use" or a zombie server.
|
|
8
|
+
- **Identity is the app, not the port.** The build plugin stamps a stable `projectId` that travels in every connection. If your Next app usually runs on `:3000` but boots on `:3001` today, Reticle still knows which app it is — and an agent scoped to project A will never accidentally drive project B's tab. Origin is only a fallback hint.
|
|
9
|
+
- **One browser, many contexts.** When agents need their own headless tabs, the daemon's **browser pool** launches a single Chromium and hands out isolated contexts (one per flow) — cheap, and capped so a big fan-out can't exhaust the machine. Over-cap requests queue.
|
|
10
|
+
- **Attach-only.** Reticle never starts your dev server. It connects to an app you're already running (or opens a headless tab pointed at it).
|
|
11
|
+
|
|
12
|
+
## Manual testing — ~5 minutes
|
|
13
|
+
|
|
14
|
+
1. Add the plugin (Vite/Next) or one `reticle.connect()` call. (See [getting-started](./getting-started.md).)
|
|
15
|
+
2. Start your app as you normally do.
|
|
16
|
+
3. Open it in a browser — the in-page panel shows Reticle is connected.
|
|
17
|
+
4. Click around; flag anything that looks wrong with the "Flag a bug" annotator. The agent drains those with `reticle_session {action:"review"}`.
|
|
18
|
+
|
|
19
|
+
## Agent testing — ~2 minutes
|
|
20
|
+
|
|
21
|
+
With the app running and instrumented, an agent drives a flow end to end:
|
|
22
|
+
|
|
23
|
+
```text
|
|
24
|
+
reticle_lease {action:"acquire"} { url: "http://localhost:3000/dashboard" }
|
|
25
|
+
→ { sessionId: "lease-…", ready: true, leased: 1, queued: 0 }
|
|
26
|
+
reticle_act { sessionId, ... } # drive the flow
|
|
27
|
+
reticle_assert { sessionId, ... } # verify intent
|
|
28
|
+
reticle_lease {action:"release"} { sessionId } # free the slot
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
`reticle_lease {action:"acquire"}` opens a fresh isolated headless context against your **already-running** app, stamps the lease identity into the URL so the app's own SDK registers under a sessionId you can target, and waits until that tab has connected (`ready: true`) before returning — so the sessionId is usable immediately. Release when the flow finishes.
|
|
32
|
+
|
|
33
|
+
## 10 agents, 10 flows, one dashboard
|
|
34
|
+
|
|
35
|
+
This is the design target, and it needs no special setup:
|
|
36
|
+
|
|
37
|
+
- Each agent calls `reticle_lease {action:"acquire"}` for the same dashboard URL → its own isolated context (own cookies/storage) in the **one** shared Chromium.
|
|
38
|
+
- The pool caps simultaneous contexts (`RETICLE_MAX_CONTEXTS`, default scales with CPU under a ceiling); extra acquires queue and proceed as slots free.
|
|
39
|
+
- Flows can't bleed into each other — contexts are isolated and every session is scoped by `projectId`.
|
|
40
|
+
- A single crashed page is reclaimed on its own, and if an agent crashes or hangs its lease stops being touched and the **lease reaper** reclaims the context after a TTL, freeing the slot. One dead agent never starves the others.
|
|
41
|
+
|
|
42
|
+
`reticle_sessions` lists everything with `projectId` (group by app) and `leased` (pool context vs a human tab), so an orchestrator can see the whole fleet at a glance.
|
|
43
|
+
|
|
44
|
+
## Knobs
|
|
45
|
+
|
|
46
|
+
| Env | Default | Effect |
|
|
47
|
+
| --- | --- | --- |
|
|
48
|
+
| `RETICLE_MAX_CONTEXTS` | `min(8, cpus-1)` | Max simultaneous leased headless contexts. |
|
|
49
|
+
| `RETICLE_PORT` | from `.reticle.json`, else `4400` | Daemon port (rarely needed — discovery handles it). |
|
|
50
|
+
|
|
51
|
+
## Why not just open many browsers?
|
|
52
|
+
|
|
53
|
+
Ten Chromiums is hundreds of MB each and will thrash a laptop. Ten contexts in one browser is a few MB apiece and fully isolated — same correctness, a fraction of the cost. That's the whole point of the pool.
|
|
54
|
+
|
|
55
|
+
**Measured** (`bench/harness/multi-agent-throughput.mjs`): 16 verification flows that take **35.4s** one-at-a-time finish in **5.2s** across 8 leased contexts on a single Chromium — **6.78× faster**, ~30s saved per batch, with all 8 contexts live at peak. The speed-up scales with agent count up to the cap; the win over launching a browser per agent grows with how much per-agent browser startup you avoid.
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# Integrating Reticle
|
|
2
|
+
|
|
3
|
+
> The one guide for adopting Reticle — for a team using a coding agent on its own app, and for an AI app-builder platform (Lovable / Emergent / Bolt) embedding Reticle in its generation pipeline. Reticle reads the program from _inside_ a running app and returns a **verdict with evidence** ("did it actually work?"), not a screenshot. Enterprise/premium access lives in [`enterprise.md`](./enterprise.md).
|
|
4
|
+
|
|
5
|
+
## The loop
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
generate / edit → boot the preview → Reticle verifies the critical flows → verdict + evidence + repair
|
|
9
|
+
│
|
|
10
|
+
PASS → ship & attach "verified ✓" │
|
|
11
|
+
FAIL → gate the deploy, feed repair packets to the fixer agent
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
One call replays the app's key journeys and asserts **program truth** — network cardinality, store/state, emitted signals, console — then returns a deterministic, un-hallucinatable verdict.
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Quickstart
|
|
19
|
+
|
|
20
|
+
### A. A team, agent on your own app (~10 min)
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npx @reticlehq/server init # auto-detects your framework, installs the kit + build plugin
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Paste to your agent (Claude Code / Cursor / any MCP agent): `Follow https://raw.githubusercontent.com/reticlehq/reticle/main/SKILL.md` It runs the wizard once (Vite/Next plugin + SDK init + MCP config), then verifies on every change. Run your dev server, then ask the agent to _"verify it with Reticle."_
|
|
27
|
+
|
|
28
|
+
### B. A platform / CI, driven from your pipeline (no MCP, no human)
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
reticle serve --http --http-token "$TOKEN" --drive "$PREVIEW_URL" # localhost:7331
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
```js
|
|
35
|
+
const { run } = await (
|
|
36
|
+
await fetch('http://127.0.0.1:7331/verify', {
|
|
37
|
+
method: 'POST',
|
|
38
|
+
headers: { 'content-type': 'application/json', 'x-reticle-token': process.env.TOKEN },
|
|
39
|
+
body: JSON.stringify({
|
|
40
|
+
project: { name, framework, previewUrl },
|
|
41
|
+
trigger: { kind: 'oem', diffRef },
|
|
42
|
+
}),
|
|
43
|
+
})
|
|
44
|
+
).json();
|
|
45
|
+
|
|
46
|
+
if (run.verdict.status !== 'pass') {
|
|
47
|
+
for (const p of run.repair?.failurePackets ?? []) fixerAgent.send(p.suggestedPrompt); // self-heal
|
|
48
|
+
blockDeploy(run);
|
|
49
|
+
} else attachToDeploy(run); // "verified ✓"; set profile:"prod-preview" to redact internals downstream
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Or skip the HTTP server entirely with the one-shot CLI — `reticle verify <preview-url>` drives the preview, replays the saved flows, prints the verdict, and exits non-zero on fail (ideal for a CI step).
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## In-app SDK integration — the effort, by layer
|
|
57
|
+
|
|
58
|
+
Reticle embeds a **dev/preview-only** SDK (`@reticlehq/browser`, Apache-2.0, tree-shaken from production). For a platform you add this **once to your generated-app template** → every generated app is verifiable.
|
|
59
|
+
|
|
60
|
+
| Layer | What you add | Unlocks | Effort |
|
|
61
|
+
| --- | --- | --- | --- |
|
|
62
|
+
| **1 — drive + DOM/network/console** | 1 build-plugin line + ~10-line dev-only `reticle.connect({…})` file (`npx @reticlehq/server init` does it) | broken routes, network status/cardinality (double-submit), console errors, persistence-after-reload | **Easy** (~15 min) |
|
|
63
|
+
| **2 — program-state truth** | `registerStore('app', store)` (1/store, pass the store itself so mutations emit diffs) + `reticle.signal('order:saved', …)` (1/consequence) + `data-testid`s | UI-vs-store desync, dead handlers, blast-radius, source mapping | **Easy–Medium** (an afternoon, once) |
|
|
64
|
+
| **3 — governance (optional)** | `registerCapabilities(...)` (signals/stores/risk zones) + recorded flows with success oracles | risk policy + sharper verdicts | **Medium**, optional |
|
|
65
|
+
|
|
66
|
+
Copyable patterns: `apps/bench-app/src/reticle-dev.ts`, `apps/next-smoke/app/reticle-dev.tsx`. Without instrumentation, Layer-1 checks still work via the driven browser; Layers 2–3 are what no out-of-page tool can see.
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## What it catches that a screenshot can't
|
|
71
|
+
|
|
72
|
+
| Silent failure (a generated app ships it) | How Reticle catches it |
|
|
73
|
+
| --- | --- |
|
|
74
|
+
| Mock data — POST 200, row shows, nothing persists | persistence/`state` oracle (doesn't survive reload) |
|
|
75
|
+
| Dead handler — looks done, store never changed | `state` desync |
|
|
76
|
+
| Double-submit — one click, two POSTs | `net { count: 1 }` |
|
|
77
|
+
| Forbidden call — a must-never-fire endpoint fired | `net { count: 0 }` |
|
|
78
|
+
| Missing validation — `"abc"` becomes data | flow oracle (error shown AND nothing created) |
|
|
79
|
+
| Silent console error — logged, UI still renders | `console { absent: true }` |
|
|
80
|
+
| UI-vs-store desync — the total lies | reads the store, contradicts the display |
|
|
81
|
+
| Blast-radius — an action corrupts unrelated state | `state { hold:true }` invariant |
|
|
82
|
+
|
|
83
|
+
Live, clickable demo of each: `apps/vibe-builder-demo/` (set `BUG_MODE=…`). Proven in CI: `packages/server/src/runs/generated-app-bugs.test.ts`.
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## Exact steps per platform
|
|
88
|
+
|
|
89
|
+
The shape is identical (in-app SDK in the template → verify in the sandbox → act on the verdict); the specifics differ by where each platform runs the preview.
|
|
90
|
+
|
|
91
|
+
### Emergent (Kubernetes pod per build, reverse-proxied preview URL)
|
|
92
|
+
|
|
93
|
+
1. Add `@reticlehq/browser` + `registerStore`/`reticle.signal` to the generated-app **scaffold** (one time).
|
|
94
|
+
2. In the build pod, alongside the preview: `reticle serve --http --http-token "$POD_TOKEN" --drive "$PREVIEW_URL"` (or import `ReticleRunner` in-process).
|
|
95
|
+
3. In the orchestrator's generate→test→iterate loop, `POST /verify` after the preview boots.
|
|
96
|
+
4. FAIL → route `repair.failurePackets[].suggestedPrompt` to the fixer subagent → re-verify (closes the loop). PASS → publish + attach the `prod-preview` run as the user-facing "verified ✓".
|
|
97
|
+
|
|
98
|
+
### Lovable (Vite/React generated apps, hosted preview)
|
|
99
|
+
|
|
100
|
+
1. Add the Reticle Vite plugin + dev-only `reticle.connect` to the project template (Lovable already templates Vite/React — it's one plugin line + the connect file).
|
|
101
|
+
2. Run `reticle serve --http --drive <preview-url>` against the preview build in the generation worker.
|
|
102
|
+
3. Call `/verify` after each generate/edit; gate the "your app is ready" signal on `verdict.status === 'pass'`; feed repair packets back into the edit agent.
|
|
103
|
+
|
|
104
|
+
### Bolt.new / StackBlitz (WebContainer, in-browser runtime)
|
|
105
|
+
|
|
106
|
+
1. Add the SDK to the WebContainer app template; the app + Reticle bridge run in the WebContainer.
|
|
107
|
+
2. Since the runtime is in-browser, drive via the connected session (the SDK dials the bridge) rather than `--drive`; call verify from the Bolt agent after a build.
|
|
108
|
+
3. Same act-on-verdict: gate + self-heal with the repair packets. (Bolt already detects terminal/compile errors; Reticle adds the _runtime program-truth_ layer it's blind to.)
|
|
109
|
+
|
|
110
|
+
> Honest note: a platform can build a verification step itself. Reticle's case is the depth (program-state and source mapping), the determinism (0% flake, no LLM in the loop), the un-hallucinatable verdict, and a stable drop-in artifact. The reproducible benchmark in [`bench/`](../bench/README.md) measures the observation-cost and detection differences against other browser-automation MCPs.
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## The verdict artifact
|
|
115
|
+
|
|
116
|
+
`POST /verify` (and `reticle_run_export`) return a stable, versioned `ReticleVerificationRun` (defined in `@reticlehq/core`): `verdict` (pass/fail/partial, confidence, blockingRisks), `flows[]`, `checks[]`, `risks[]` (auth/payment/db/…), `repair.failurePackets[]` (what + where to fix), `evidence`. Render a legible report with `renderRunReport()` or `reticle_run_export { format: "report" }`. Profiles: `dev` (full) vs `prod-preview` (source + state redacted for downstream sharing).
|
|
117
|
+
|
|
118
|
+
**Why trust it:** the verdict is mechanical — derived only from observed outcomes — so it can't report green for something it never ran (a severed backend reads as _fail_, never a confident pass). Proof: `packages/server/src/runs/false-green.test.ts`.
|
|
119
|
+
|
|
120
|
+
## Licensing for embedding
|
|
121
|
+
|
|
122
|
+
The embeddable SDK is **Apache-2.0** (ship it in your customers' apps). The server/CLI is **FSL** (free, no competing resale). Enterprise features + the premium-access flow: [`enterprise.md`](./enterprise.md). OEM terms: **[hey@reticle.sh](mailto:hey@reticle.sh)**.
|