@reticlehq/server 2.4.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} +63 -13
- package/dist/{bridge.test-harness.d.ts → bridge/bridge.test-harness.d.ts} +1 -11
- package/dist/{bridge.test-harness.js → bridge/bridge.test-harness.js} +9 -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-parse.d.ts → cli/cli-parse.d.ts} +2 -11
- package/dist/{cli-parse.js → cli/cli-parse.js} +113 -60
- package/dist/{cli-port.d.ts → cli/cli-port.d.ts} +12 -0
- package/dist/{cli-port.js → cli/cli-port.js} +9 -9
- package/dist/cli/cli-update-commands.d.ts +4 -0
- package/dist/cli/cli-update-commands.js +93 -0
- package/dist/{cli-verify.js → cli/cli-verify.js} +14 -14
- package/dist/{cloud-cli.js → cli/cloud-cli.js} +29 -24
- package/dist/cli.d.ts +2 -2
- package/dist/cli.js +174 -92
- 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.js +24 -9
- package/dist/flows/empty-flow.d.ts +6 -0
- package/dist/flows/empty-flow.js +24 -0
- package/dist/flows/flake-store.d.ts +0 -1
- package/dist/flows/flake-store.js +1 -1
- package/dist/flows/flake.d.ts +2 -1
- package/dist/flows/flake.js +2 -2
- package/dist/flows/flow-classify.js +12 -0
- package/dist/flows/flow-replay-run.js +4 -4
- package/dist/flows/flow-replay.d.ts +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 +53 -7
- 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.js +4 -1
- 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 +3 -2
- package/dist/init/detect.js +16 -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 +2 -4
- package/dist/init/next-patch.js +109 -19
- package/dist/init/node-io.js +23 -6
- package/dist/init/plan-framework.d.ts +16 -1
- package/dist/init/plan-framework.js +132 -8
- package/dist/init/plan.d.ts +73 -8
- package/dist/init/plan.js +133 -34
- package/dist/init/project-id.d.ts +5 -9
- package/dist/init/project-id.js +10 -22
- package/dist/init/run.d.ts +5 -0
- package/dist/init/run.js +210 -36
- package/dist/init/slash-command.d.ts +9 -0
- package/dist/init/slash-command.js +10 -1
- package/dist/init/snippets.d.ts +7 -10
- package/dist/init/snippets.js +44 -15
- package/dist/init/vite-config.js +1 -1
- package/dist/init/workspace-apps.d.ts +23 -0
- package/dist/init/workspace-apps.js +68 -0
- package/dist/input/driven-redaction.js +1 -1
- package/dist/input/network-detail.js +8 -8
- package/dist/input/network-mock-tools.js +2 -2
- package/dist/input/network-mock.js +4 -4
- package/dist/input/real-input.js +8 -3
- package/dist/input/scroll-find.js +2 -2
- package/dist/input/scroll-tools.d.ts +0 -5
- package/dist/input/scroll-tools.js +11 -4
- package/dist/input/viewport-tools.js +2 -2
- package/dist/journal/ambient-store.d.ts +0 -2
- package/dist/journal/ambient-store.js +1 -1
- package/dist/journal/ambient.js +1 -1
- package/dist/journal/attach-journal.d.ts +2 -1
- package/dist/journal/deviation-report.js +6 -6
- package/dist/journal/deviation-service.js +1 -1
- package/dist/journal/envelope-store.d.ts +0 -2
- package/dist/journal/envelope-store.js +1 -1
- package/dist/journal/envelope.d.ts +2 -3
- package/dist/journal/envelope.js +3 -3
- package/dist/journal/journal-query.js +1 -1
- package/dist/journal/journal-recorder.d.ts +2 -1
- package/dist/journal/journal-recorder.js +2 -2
- package/dist/journal/rollups.js +9 -9
- package/dist/journal/session-end.d.ts +2 -1
- package/dist/journal/session-journal.js +4 -4
- package/dist/license/license.d.ts +0 -14
- package/dist/license/license.js +8 -8
- package/dist/log.d.ts +10 -0
- package/dist/log.js +11 -1
- package/dist/mcp/mcp-is-error.d.ts +13 -0
- package/dist/mcp/mcp-is-error.js +18 -0
- package/dist/mcp/mcp-outage.d.ts +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 +52 -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 +14 -2
- package/dist/session/session.js +69 -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 +81 -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/daemon-resilience.d.ts +0 -22
- package/dist/daemon-resilience.js +0 -85
- package/dist/mcp-proxy.d.ts +0 -67
- package/dist/mcp-proxy.js +0 -337
- package/dist/tools/profiles.d.ts +0 -37
- package/dist/tools/profiles.js +0 -139
- /package/dist/{pairing-token.d.ts → bridge/pairing-token.d.ts} +0 -0
- /package/dist/{token-auth.d.ts → bridge/token-auth.d.ts} +0 -0
- /package/dist/{token-auth.js → bridge/token-auth.js} +0 -0
- /package/dist/{cli-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
package/dist/init/run.js
CHANGED
|
@@ -4,7 +4,42 @@
|
|
|
4
4
|
* access goes through `InitIo` so the orchestration is unit-testable with an in-memory IO.
|
|
5
5
|
*/
|
|
6
6
|
import { dirname, join } from 'node:path';
|
|
7
|
+
import { spanSync } from '../trace.js';
|
|
8
|
+
import { readFileSync } from 'node:fs';
|
|
9
|
+
import { homedir } from 'node:os';
|
|
7
10
|
import { detect, Framework } from './detect.js';
|
|
11
|
+
import { wasMcpRegistered } from './mcp-registered.js';
|
|
12
|
+
import { pickAstroHost } from './astro-host.js';
|
|
13
|
+
import { workspaceParents } from './workspace-apps.js';
|
|
14
|
+
import { chooseWorkspaceApp } from './app-choice.js';
|
|
15
|
+
import { isConnectStep } from './plan.js';
|
|
16
|
+
import { CURSOR_RULE_PATH } from './agent-rules.js';
|
|
17
|
+
import { CRA_ENV_PATH } from './cra.js';
|
|
18
|
+
/** CRA's bundled entry, in the order create-react-app itself generates them. */
|
|
19
|
+
const CRA_ENTRY_CANDIDATES = ['src/index.tsx', 'src/index.jsx', 'src/index.ts', 'src/index.js'];
|
|
20
|
+
function craEntryOf(io) {
|
|
21
|
+
for (const path of CRA_ENTRY_CANDIDATES) {
|
|
22
|
+
const source = io.readFile(path);
|
|
23
|
+
if (source !== null)
|
|
24
|
+
return { path, source };
|
|
25
|
+
}
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* The daemon's pairing token, for the one stack that cannot read it at build time.
|
|
30
|
+
*
|
|
31
|
+
* Every other framework's snippet reads this file in Node when the dev server starts, so the token
|
|
32
|
+
* is never written into the project. CRA bundles browser code and inlines only REACT_APP_*, so it
|
|
33
|
+
* has to be materialised into .env.development.local — which CRA's own template gitignores.
|
|
34
|
+
*/
|
|
35
|
+
function readPairingToken() {
|
|
36
|
+
try {
|
|
37
|
+
return readFileSync(join(homedir(), '.reticle', 'pairing-token'), 'utf8').trim();
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
return '';
|
|
41
|
+
}
|
|
42
|
+
}
|
|
8
43
|
import { DEPS_TARGET, MCP_TARGET, buildPlan, StepStatus, } from './plan.js';
|
|
9
44
|
import { claudeAvailableProbe, claudeExistsProbe } from './mcp.js';
|
|
10
45
|
import { reticleDevLocation } from './next-patch.js';
|
|
@@ -13,7 +48,7 @@ import { CURSOR_DIR_RELPATH, CURSOR_MCP_RELPATH } from './cursor.js';
|
|
|
13
48
|
import { deriveProjectId, packageName } from './project-id.js';
|
|
14
49
|
import { VITE_DEV_MODULE_PATH } from './snippets.js';
|
|
15
50
|
import { CLAUDE_COMMAND_PATH, CURSOR_COMMAND_PATH } from './slash-command.js';
|
|
16
|
-
import { SERVER_VERSION } from '../server-version.js';
|
|
51
|
+
import { SERVER_VERSION } from '../version/server-version.js';
|
|
17
52
|
import { InitFailure, reportInitOutcome } from '../telemetry/init-telemetry.js';
|
|
18
53
|
/** Lockfile basenames, in package-manager preference order (mirrors detect.ts). */
|
|
19
54
|
const LOCKFILE_NAMES = [
|
|
@@ -108,6 +143,16 @@ const VITE_CONFIG_CANDIDATES = [
|
|
|
108
143
|
'vite.config.mjs',
|
|
109
144
|
'vite.config.mts',
|
|
110
145
|
];
|
|
146
|
+
const ASTRO_CONFIG_CANDIDATES = [
|
|
147
|
+
'astro.config.mjs',
|
|
148
|
+
'astro.config.js',
|
|
149
|
+
'astro.config.ts',
|
|
150
|
+
'astro.config.cjs',
|
|
151
|
+
];
|
|
152
|
+
/** Where a conventional Astro app keeps the layout every page wraps itself in. */
|
|
153
|
+
const ASTRO_LAYOUTS_DIR = 'src/layouts';
|
|
154
|
+
/** Also searched: an app with no layouts directory renders the document straight from a page. */
|
|
155
|
+
const ASTRO_PAGES_DIR = 'src/pages';
|
|
111
156
|
const NEXT_CONFIG_CANDIDATES = [
|
|
112
157
|
'next.config.mjs',
|
|
113
158
|
'next.config.js',
|
|
@@ -119,6 +164,21 @@ const NEXT_CONFIG_CANDIDATES = [
|
|
|
119
164
|
* decide whether the install finished. A notice gets its own mark so "steps remaining" can reach zero
|
|
120
165
|
* on a working install that happens to be on an ungated stack.
|
|
121
166
|
*/
|
|
167
|
+
/**
|
|
168
|
+
* A step's status AFTER the run, which is what actually happened to it.
|
|
169
|
+
*
|
|
170
|
+
* `report` applies the same downgrade when printing: a step that failed or was skipped is shown as
|
|
171
|
+
* MANUAL whatever it planned to be. Reading the planned status alone would say a step applied when
|
|
172
|
+
* the run had already given up on it.
|
|
173
|
+
*/
|
|
174
|
+
function resolvedStatus(plan, target, failed, skipped) {
|
|
175
|
+
const step = plan.steps.find((s) => s.target === target);
|
|
176
|
+
if (step === undefined)
|
|
177
|
+
return undefined;
|
|
178
|
+
if (failed.has(target) || skipped.has(target))
|
|
179
|
+
return StepStatus.MANUAL;
|
|
180
|
+
return step.status;
|
|
181
|
+
}
|
|
122
182
|
const STATUS_SYMBOL = {
|
|
123
183
|
[StepStatus.APPLY]: '✓',
|
|
124
184
|
[StepStatus.MANUAL]: '⚠',
|
|
@@ -138,7 +198,7 @@ function gatherPlanInput(options, io, pkgRaw) {
|
|
|
138
198
|
const projectId = deriveProjectId(packageName(pkg), options.cwd);
|
|
139
199
|
const rootFiles = new Set(io.rootFiles());
|
|
140
200
|
const detectInput = {
|
|
141
|
-
pkg: typeof pkg
|
|
201
|
+
pkg: 'object' === typeof pkg && pkg !== null ? pkg : {},
|
|
142
202
|
configFiles: rootFiles,
|
|
143
203
|
// Walk up for the lockfile so a monorepo sub-package picks the workspace's package manager.
|
|
144
204
|
lockfiles: resolveLockfiles(rootFiles, options.cwd, io),
|
|
@@ -147,7 +207,7 @@ function gatherPlanInput(options, io, pkgRaw) {
|
|
|
147
207
|
};
|
|
148
208
|
const detection = detect(detectInput);
|
|
149
209
|
const vitePath = firstPresent(rootFiles, VITE_CONFIG_CANDIDATES);
|
|
150
|
-
const viteSource =
|
|
210
|
+
const viteSource = null === vitePath ? null : io.readFile(vitePath);
|
|
151
211
|
const viteConfig = vitePath !== null && viteSource !== null ? { path: vitePath, source: viteSource } : null;
|
|
152
212
|
// Global MCP registration targets each agent that's present: Claude via its CLI, Cursor via its
|
|
153
213
|
// global config file. Only probe when the MCP step is in play.
|
|
@@ -155,16 +215,36 @@ function gatherPlanInput(options, io, pkgRaw) {
|
|
|
155
215
|
const claudeCli = options.mcp ? io.probe(availableProbe.command, availableProbe.args) : false;
|
|
156
216
|
const existsProbe = claudeExistsProbe();
|
|
157
217
|
const mcpExists = claudeCli ? io.probe(existsProbe.command, existsProbe.args) : false;
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
const
|
|
218
|
+
// join(), not a literal '/': this path is checked and WRITTEN on the user's machine, and a
|
|
219
|
+
// backslash platform silently misses the config it is meant to find.
|
|
220
|
+
const cursorDir = join(io.homeDir(), CURSOR_DIR_RELPATH);
|
|
221
|
+
const cursorConfigPath = join(io.homeDir(), CURSOR_MCP_RELPATH);
|
|
222
|
+
// Cursor is "present" if its global config directory exists OR the project carries a `.cursor/`.
|
|
223
|
+
// The directory alone missed a real case: Cursor installed with a FRESH profile has not written
|
|
224
|
+
// ~/.cursor yet, so a user who had it open got no Cursor step and no message about one. A
|
|
225
|
+
// project-level .cursor/ is unambiguous evidence somebody uses it here.
|
|
226
|
+
const cursorPresent = options.mcp && (io.exists(cursorDir) || io.exists(CURSOR_DIR_RELPATH));
|
|
161
227
|
const cursorConfig = cursorPresent ? io.readFile(cursorConfigPath) : null;
|
|
228
|
+
const astroPath = firstPresent(rootFiles, ASTRO_CONFIG_CANDIDATES);
|
|
229
|
+
const astroSource = null === astroPath ? null : io.readFile(astroPath);
|
|
230
|
+
// Which file owns the DOCUMENT, not how many files sit in a directory. The old rule ("exactly one
|
|
231
|
+
// .astro in src/layouts") fired on neither real Astro app: one has no layouts directory and
|
|
232
|
+
// renders from src/pages/index.astro, the other has three files there of which two are partials.
|
|
233
|
+
// `</body>` is the discriminator — see astro-host.
|
|
234
|
+
const astroCandidates = [ASTRO_LAYOUTS_DIR, ASTRO_PAGES_DIR].flatMap((dir) => io
|
|
235
|
+
.listFiles(dir)
|
|
236
|
+
.filter((f) => f.endsWith('.astro'))
|
|
237
|
+
.map((f) => ({ path: `${dir}/${f}`, source: io.readFile(`${dir}/${f}`) }))
|
|
238
|
+
.filter((c) => c.source !== null));
|
|
239
|
+
const astroHost = pickAstroHost(astroCandidates);
|
|
240
|
+
const layoutRelPath = astroHost?.path ?? null;
|
|
241
|
+
const astroLayoutSource = astroHost?.source ?? null;
|
|
162
242
|
const nextConfigFile = firstPresent(rootFiles, NEXT_CONFIG_CANDIDATES);
|
|
163
243
|
// App Router first; a Pages Router app has no layout, and its mount point is pages/_app.
|
|
164
|
-
const layoutPath =
|
|
165
|
-
NEXT_PAGES_APP_CANDIDATES.find((p) => io.exists(p))
|
|
244
|
+
const layoutPath = NEXT_LAYOUT_CANDIDATES.find((p) => io.exists(p)) ??
|
|
245
|
+
NEXT_PAGES_APP_CANDIDATES.find((p) => io.exists(p)) ??
|
|
166
246
|
null;
|
|
167
|
-
const layoutSource =
|
|
247
|
+
const layoutSource = null === layoutPath ? null : io.readFile(layoutPath);
|
|
168
248
|
// Where the component goes depends on WHICH router mounts it: `pages/` routes on presence, so a
|
|
169
249
|
// component there becomes a broken route; `app/` routes on filename, so a sibling is inert.
|
|
170
250
|
const devLocation = reticleDevLocation(layoutPath ?? 'app/layout.tsx', detection.typescript);
|
|
@@ -177,8 +257,12 @@ function gatherPlanInput(options, io, pkgRaw) {
|
|
|
177
257
|
cursorConfig,
|
|
178
258
|
cursorConfigPath,
|
|
179
259
|
viteConfig,
|
|
260
|
+
astroConfig: astroPath !== null && astroSource !== null ? { path: astroPath, source: astroSource } : null,
|
|
261
|
+
astroLayout: layoutRelPath !== null && astroLayoutSource !== null
|
|
262
|
+
? { path: layoutRelPath, source: astroLayoutSource }
|
|
263
|
+
: null,
|
|
180
264
|
nextConfigFile,
|
|
181
|
-
nextConfigSource:
|
|
265
|
+
nextConfigSource: null === nextConfigFile ? null : io.readFile(nextConfigFile),
|
|
182
266
|
nextLayout: layoutPath !== null && layoutSource !== null
|
|
183
267
|
? { path: layoutPath, source: layoutSource }
|
|
184
268
|
: null,
|
|
@@ -190,13 +274,16 @@ function gatherPlanInput(options, io, pkgRaw) {
|
|
|
190
274
|
nextReticleDevImport: devLocation.importSpecifier,
|
|
191
275
|
nextReticleDevExists: io.exists(devLocation.path),
|
|
192
276
|
svelteKitHooksExists: io.exists(SVELTEKIT_HOOKS),
|
|
277
|
+
craEntry: craEntryOf(io),
|
|
278
|
+
craEnv: io.readFile(CRA_ENV_PATH),
|
|
279
|
+
pairingToken: readPairingToken(),
|
|
193
280
|
reticleConfigExists: io.exists('.reticle.json'),
|
|
194
281
|
// Read the agent instruction files so the rule merge stays idempotent across re-runs.
|
|
195
282
|
claudeMdContent: io.readFile('CLAUDE.md'),
|
|
196
283
|
agentsMdContent: io.readFile('AGENTS.md'),
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
284
|
+
cursorRuleContent: io.readFile(CURSOR_RULE_PATH),
|
|
285
|
+
claudeCommandContent: io.readFile(CLAUDE_COMMAND_PATH),
|
|
286
|
+
cursorCommandContent: io.readFile(CURSOR_COMMAND_PATH),
|
|
200
287
|
options: {
|
|
201
288
|
port: options.port,
|
|
202
289
|
mcp: options.mcp,
|
|
@@ -208,12 +295,13 @@ function gatherPlanInput(options, io, pkgRaw) {
|
|
|
208
295
|
};
|
|
209
296
|
}
|
|
210
297
|
/** Where workspace tooling conventionally puts packages. */
|
|
211
|
-
|
|
298
|
+
/** pnpm's workspace declaration, read when present — it is authoritative about where packages live. */
|
|
299
|
+
const PNPM_WORKSPACE = 'pnpm-workspace.yaml';
|
|
212
300
|
/** Deps that mark a directory as a runnable web app even when it has no bundler config file. */
|
|
213
301
|
const APP_DEPS = ['next', 'vite'];
|
|
214
302
|
function looksLikeApp(dir, io) {
|
|
215
303
|
const pkgRaw = io.readFile(`${dir}/${PACKAGE_JSON}`);
|
|
216
|
-
if (
|
|
304
|
+
if (null === pkgRaw)
|
|
217
305
|
return false;
|
|
218
306
|
const configs = [...VITE_CONFIG_CANDIDATES, ...NEXT_CONFIG_CANDIDATES];
|
|
219
307
|
if (configs.some((c) => io.exists(`${dir}/${c}`)))
|
|
@@ -231,14 +319,40 @@ function looksLikeApp(dir, io) {
|
|
|
231
319
|
*/
|
|
232
320
|
export function findWorkspaceApps(io) {
|
|
233
321
|
const found = [];
|
|
234
|
-
|
|
322
|
+
// A workspace DECLARES its packages; `['apps','packages']` was a guess that missed a real repo
|
|
323
|
+
// with three Next apps at web/, admin/ and space/ — and missing them meant init ran against the
|
|
324
|
+
// root and reported ✓ for a file Next never compiles. See workspace-apps.
|
|
325
|
+
const pkgRaw = io.readFile(PACKAGE_JSON);
|
|
326
|
+
let pkgWorkspaces;
|
|
327
|
+
try {
|
|
328
|
+
const parsed = null === pkgRaw ? undefined : JSON.parse(pkgRaw);
|
|
329
|
+
pkgWorkspaces =
|
|
330
|
+
'object' === typeof parsed && parsed !== null
|
|
331
|
+
? parsed.workspaces
|
|
332
|
+
: undefined;
|
|
333
|
+
}
|
|
334
|
+
catch {
|
|
335
|
+
pkgWorkspaces = undefined;
|
|
336
|
+
}
|
|
337
|
+
const parents = workspaceParents({
|
|
338
|
+
...(null === io.readFile(PNPM_WORKSPACE)
|
|
339
|
+
? {}
|
|
340
|
+
: { pnpmWorkspace: io.readFile(PNPM_WORKSPACE) ?? '' }),
|
|
341
|
+
...(pkgWorkspaces === undefined ? {} : { pkgWorkspaces }),
|
|
342
|
+
topLevelDirs: io.listDirs('.'),
|
|
343
|
+
});
|
|
344
|
+
// A declared parent can itself BE the app (`workspaces: ["web"]`), so check both the directory and
|
|
345
|
+
// its children rather than assuming one level of nesting.
|
|
346
|
+
for (const parent of parents) {
|
|
347
|
+
if (looksLikeApp(parent, io))
|
|
348
|
+
found.push(parent);
|
|
235
349
|
for (const name of io.listDirs(parent)) {
|
|
236
350
|
const dir = `${parent}/${name}`;
|
|
237
351
|
if (looksLikeApp(dir, io))
|
|
238
352
|
found.push(dir);
|
|
239
353
|
}
|
|
240
354
|
}
|
|
241
|
-
return found;
|
|
355
|
+
return [...new Set(found)];
|
|
242
356
|
}
|
|
243
357
|
function restartHint(framework) {
|
|
244
358
|
if (framework === Framework.NEXT)
|
|
@@ -253,13 +367,26 @@ function restartHint(framework) {
|
|
|
253
367
|
}
|
|
254
368
|
const SKIPPED_DETAIL = 'skipped — the dependency install above failed, and wiring the app to a package that is not ' +
|
|
255
369
|
'installed stops it booting. Run that install, then re-run `reticle init`.';
|
|
256
|
-
function report(plan, dryRun, failed, skipped, io) {
|
|
370
|
+
function report(plan, dryRun, failed, skipped, degraded, io) {
|
|
257
371
|
io.print(dryRun ? 'reticle init (dry run — no files written)' : 'reticle init');
|
|
258
372
|
io.print('');
|
|
259
373
|
let applied = 0;
|
|
260
374
|
let manual = 0;
|
|
375
|
+
// A ⚠ on a CONNECT step is a guaranteed failure, not a caveat: nothing performs the manual step, so
|
|
376
|
+
// the app never dials the daemon and every tool answers "no browser session connected". `ok` was
|
|
377
|
+
// hardcoded true, so a run that could not possibly work reported success.
|
|
378
|
+
let connectPending = false;
|
|
261
379
|
for (const s of plan.steps) {
|
|
262
380
|
// A side effect that failed to apply is reported as a manual step with its fallback command.
|
|
381
|
+
const note = degraded.get(s.target);
|
|
382
|
+
if (note !== undefined) {
|
|
383
|
+
// Applied, but not the way it was asked for. A NOTICE, not work — the install did happen.
|
|
384
|
+
io.print(` [${STATUS_SYMBOL[StepStatus.NOTICE]}] ${s.title} → ${s.target}`);
|
|
385
|
+
for (const line of note.split('\n'))
|
|
386
|
+
io.print(` ${line}`);
|
|
387
|
+
applied++;
|
|
388
|
+
continue;
|
|
389
|
+
}
|
|
263
390
|
const downgraded = failed.has(s.target) || skipped.has(s.target);
|
|
264
391
|
const status = downgraded ? StepStatus.MANUAL : s.status;
|
|
265
392
|
const detail = skipped.has(s.target)
|
|
@@ -272,8 +399,11 @@ function report(plan, dryRun, failed, skipped, io) {
|
|
|
272
399
|
applied++;
|
|
273
400
|
if (status === StepStatus.MANUAL || status === StepStatus.NOTICE) {
|
|
274
401
|
// A notice prints in full like a manual step — it is worth reading — but is NOT counted as work.
|
|
275
|
-
if (status === StepStatus.MANUAL)
|
|
402
|
+
if (status === StepStatus.MANUAL) {
|
|
276
403
|
manual++;
|
|
404
|
+
if (isConnectStep(s.title))
|
|
405
|
+
connectPending = true;
|
|
406
|
+
}
|
|
277
407
|
for (const line of detail.split('\n'))
|
|
278
408
|
io.print(` ${line}`);
|
|
279
409
|
}
|
|
@@ -282,8 +412,13 @@ function report(plan, dryRun, failed, skipped, io) {
|
|
|
282
412
|
}
|
|
283
413
|
}
|
|
284
414
|
io.print('');
|
|
415
|
+
if (connectPending) {
|
|
416
|
+
io.print('This app will NOT connect until the ⚠ step above is done by hand — Reticle tools will report ' +
|
|
417
|
+
'"no browser session connected" until then. Everything else is already in place.');
|
|
418
|
+
io.print('');
|
|
419
|
+
}
|
|
285
420
|
io.print(restartHint(plan.framework));
|
|
286
|
-
return { ok:
|
|
421
|
+
return { ok: !connectPending, applied, manual };
|
|
287
422
|
}
|
|
288
423
|
/**
|
|
289
424
|
* Perform the apply-step side effects; return the targets whose side effect failed.
|
|
@@ -297,23 +432,48 @@ function report(plan, dryRun, failed, skipped, io) {
|
|
|
297
432
|
function applyEffects(plan, io) {
|
|
298
433
|
const failed = new Set();
|
|
299
434
|
const skipped = new Set();
|
|
435
|
+
const degraded = new Map();
|
|
300
436
|
let installFailed = false;
|
|
301
437
|
for (const s of plan.steps) {
|
|
302
438
|
if (s.status !== StepStatus.APPLY)
|
|
303
439
|
continue;
|
|
304
|
-
if (installFailed && s.dependsOnInstall
|
|
440
|
+
if (installFailed && true === s.dependsOnInstall) {
|
|
305
441
|
skipped.add(s.target);
|
|
306
442
|
continue;
|
|
307
443
|
}
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
444
|
+
const write = s.write;
|
|
445
|
+
if (write !== undefined) {
|
|
446
|
+
spanSync('init.write', { target: s.target, path: write.path }, () => {
|
|
447
|
+
io.writeFile(write.path, write.content);
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
// Bound once so the traced call cannot need a `?? ''` fallback — a default there would turn a
|
|
451
|
+
// narrowing mistake into an empty command that silently "succeeds".
|
|
452
|
+
const exec = s.exec;
|
|
453
|
+
// Per-step, because the interesting part of init's wall-clock is WHICH step spent it: a
|
|
454
|
+
// package-manager install and a `claude mcp add` are both subprocesses, and one of them being
|
|
455
|
+
// slow is a completely different problem from the other.
|
|
456
|
+
if (exec !== undefined &&
|
|
457
|
+
!spanSync('init.exec', { target: s.target, command: exec.command }, () => io.exec(exec.command, exec.args))) {
|
|
458
|
+
// A weaker second attempt beats no install at all — but only when it is REPORTED, because the
|
|
459
|
+
// thing it gives up is the version pin that keeps SDK and daemon in step.
|
|
460
|
+
//
|
|
461
|
+
// Traced separately, and it is the reason init can take twice as long as it looks like it
|
|
462
|
+
// should: this is a SECOND full package-manager run, and until it had its own span 1.6 of
|
|
463
|
+
// init's 2.3 seconds simply vanished — the span above accounted for the first attempt and
|
|
464
|
+
// nothing accounted for this one.
|
|
465
|
+
const retry = s.retry;
|
|
466
|
+
if (retry !== undefined &&
|
|
467
|
+
spanSync('init.exec.retry', { target: s.target, command: retry.command }, () => io.exec(retry.command, retry.args))) {
|
|
468
|
+
degraded.set(s.target, retry.note);
|
|
469
|
+
continue;
|
|
470
|
+
}
|
|
311
471
|
failed.add(s.target);
|
|
312
472
|
if (s.target === DEPS_TARGET)
|
|
313
473
|
installFailed = true;
|
|
314
474
|
}
|
|
315
475
|
}
|
|
316
|
-
return { failed, skipped };
|
|
476
|
+
return { failed, skipped, degraded };
|
|
317
477
|
}
|
|
318
478
|
/**
|
|
319
479
|
* Which STEP failed, from the step targets — not from an error string, which would carry paths.
|
|
@@ -338,25 +498,35 @@ const AMBIGUOUS_HEADER = 'Several apps found in this workspace. Re-run `reticle
|
|
|
338
498
|
* Returns null when there is nothing to redirect to — the caller then proceeds here as before.
|
|
339
499
|
*/
|
|
340
500
|
function redirectToWorkspaceApp(options, io, pkgRaw) {
|
|
341
|
-
if (options.redirected
|
|
501
|
+
if (true === options.redirected)
|
|
342
502
|
return null;
|
|
343
503
|
const pkg = JSON.parse(pkgRaw);
|
|
344
504
|
const rootFiles = new Set(io.rootFiles());
|
|
345
505
|
const here = detect({
|
|
346
|
-
pkg: typeof pkg
|
|
506
|
+
pkg: 'object' === typeof pkg && pkg !== null ? pkg : {},
|
|
347
507
|
configFiles: rootFiles,
|
|
348
508
|
lockfiles: new Set(),
|
|
349
509
|
});
|
|
350
510
|
if (here.framework !== Framework.HTML)
|
|
351
511
|
return null; // this directory IS the app
|
|
352
512
|
const apps = findWorkspaceApps(io);
|
|
353
|
-
|
|
513
|
+
// An explicitly named app answers the ambiguity. Refusing to guess is right, but "re-run inside the
|
|
514
|
+
// one you want" is not something a script, a CI step, or an agent that cannot change directory can
|
|
515
|
+
// act on — so the refusal was a dead end for exactly the callers most likely to hit it.
|
|
516
|
+
const chosen = chooseWorkspaceApp(options.app, apps);
|
|
517
|
+
if (!chosen.ok) {
|
|
518
|
+
io.print(chosen.message);
|
|
519
|
+
return { ok: false, applied: 0, manual: 1 };
|
|
520
|
+
}
|
|
521
|
+
const target = chosen.app ?? (1 === apps.length ? apps[0] : undefined);
|
|
354
522
|
if (target === undefined) {
|
|
355
|
-
if (apps.length
|
|
523
|
+
if (0 === apps.length)
|
|
356
524
|
return null; // not a workspace — fall through to the normal HTML plan
|
|
357
525
|
io.print(AMBIGUOUS_HEADER);
|
|
358
526
|
for (const a of apps)
|
|
359
527
|
io.print(` ${a}`);
|
|
528
|
+
io.print('');
|
|
529
|
+
io.print(`Or name one without changing directory: reticle init --app ${apps[0] ?? '<dir>'}`);
|
|
360
530
|
return { ok: false, applied: 0, manual: apps.length };
|
|
361
531
|
}
|
|
362
532
|
io.print(`No app in this directory — wiring ${target} instead.`);
|
|
@@ -365,7 +535,7 @@ function redirectToWorkspaceApp(options, io, pkgRaw) {
|
|
|
365
535
|
}
|
|
366
536
|
export function runInit(options, io) {
|
|
367
537
|
const pkgRaw = io.readFile(PACKAGE_JSON);
|
|
368
|
-
if (
|
|
538
|
+
if (null === pkgRaw) {
|
|
369
539
|
io.print('No package.json found. Run `reticle init` from your project root.');
|
|
370
540
|
// The onboarding funnel had NO instrumentation, so a setup that died here was indistinguishable
|
|
371
541
|
// from someone who never ran the command — the two failure modes with the most different fixes.
|
|
@@ -375,19 +545,23 @@ export function runInit(options, io) {
|
|
|
375
545
|
const redirected = redirectToWorkspaceApp(options, io, pkgRaw);
|
|
376
546
|
if (redirected !== null)
|
|
377
547
|
return redirected;
|
|
378
|
-
|
|
548
|
+
// Init is the flow a user experiences the wait of personally, and the fixture gate measures it at
|
|
549
|
+
// 1–6s per app with no explanation of the spread. These three spans split that number into detect
|
|
550
|
+
// (filesystem probing), plan (pure), and apply (writes + package-manager and CLI subprocesses).
|
|
551
|
+
const plan = spanSync('init.plan', {}, () => buildPlan(gatherPlanInput(options, io, pkgRaw)));
|
|
379
552
|
const effects = options.dryRun
|
|
380
|
-
? { failed: new Set(), skipped: new Set() }
|
|
381
|
-
: applyEffects(plan, io);
|
|
382
|
-
const { failed, skipped } = effects;
|
|
383
|
-
const result = report(plan, options.dryRun, failed, skipped, io);
|
|
553
|
+
? { failed: new Set(), skipped: new Set(), degraded: new Map() }
|
|
554
|
+
: spanSync('init.apply', { steps: plan.steps.length }, () => applyEffects(plan, io));
|
|
555
|
+
const { failed, skipped, degraded } = effects;
|
|
556
|
+
const result = report(plan, options.dryRun, failed, skipped, degraded, io);
|
|
384
557
|
// A dry run is a preview, not an outcome — reporting it would inflate both success and failure.
|
|
385
558
|
if (!options.dryRun) {
|
|
386
559
|
reportInitOutcome({
|
|
387
560
|
ok: result.ok,
|
|
388
561
|
...(result.ok ? {} : { reason: classifyInitFailure(failed) }),
|
|
389
562
|
stack: plan.framework,
|
|
390
|
-
|
|
563
|
+
// The step's REAL final status, not the absence of a failure — see mcp-registered.
|
|
564
|
+
mcpRegistered: wasMcpRegistered(resolvedStatus(plan, MCP_TARGET, failed, skipped)),
|
|
391
565
|
});
|
|
392
566
|
}
|
|
393
567
|
return result;
|
|
@@ -15,4 +15,13 @@
|
|
|
15
15
|
export declare const CLAUDE_COMMAND_PATH = ".claude/commands/reticle.md";
|
|
16
16
|
/** Cursor reads them from `.cursor/commands/<name>.md`. */
|
|
17
17
|
export declare const CURSOR_COMMAND_PATH = ".cursor/commands/reticle.md";
|
|
18
|
+
/**
|
|
19
|
+
* The frontmatter line that marks a command file as OURS.
|
|
20
|
+
*
|
|
21
|
+
* A stale command has to be refreshable — a file frozen at whatever release created it can never be
|
|
22
|
+
* improved for anyone who already ran init — but the path is one a human may have claimed for their
|
|
23
|
+
* own `/reticle`, and overwriting that would be destroying somebody's work. This line is how the two
|
|
24
|
+
* are told apart.
|
|
25
|
+
*/
|
|
26
|
+
export declare const SLASH_COMMAND_SIGNATURE = "description: Verify this app in the browser with Reticle";
|
|
18
27
|
export declare const SLASH_COMMAND_BODY = "---\ndescription: Verify this app in the browser with Reticle \u2014 drive one real flow and report what happened.\n---\n\nVerify this running app with Reticle. Drive it; do not read the code and guess.\n\n## Pick ONE flow\n\nNot the whole app. Pick the single most important flow you can complete in a handful of steps \u2014 the\none a user would do first (sign in, search, add to cart, submit the form). If the user named a flow or\njust changed some code, use that one instead.\n\nState which flow you picked in one line before you start.\n\n## Drive it where the human can see\n\n1. `reticle_sessions` \u2014 find the connected tab. None? Tell the user to run their dev server and open\n the app, then stop. Reticle never starts a dev server.\n2. `reticle_snapshot` \u2014 read the accessibility tree and locate the elements the flow needs. Elements\n are addressable by role and name; you do **not** need `data-testid` to drive them.\n3. Walk the flow with `reticle_act_and_wait`, one step at a time. Narrate each step first \u2014 the human\n is watching the page, and the HUD shows what you say.\n4. After each step, check the effect with `reticle_assert` \u2014 not just that the click dispatched, but\n that the thing it was supposed to do actually happened.\n5. Finish with `reticle_console` and `reticle_network` to catch errors the DOM does not show.\n\n## Report\n\n- What you drove, step by step, and what each step actually produced.\n- Anything broken, with the `file:line` Reticle gave you.\n- If a step could not be verified, say so plainly. \"Unknown\" is a real answer; a green verdict you\n cannot back is not.\n\nDo not weaken or skip an assertion to make the run pass \u2014 that is a finding, not a fix.\n";
|
|
@@ -15,8 +15,17 @@
|
|
|
15
15
|
export const CLAUDE_COMMAND_PATH = '.claude/commands/reticle.md';
|
|
16
16
|
/** Cursor reads them from `.cursor/commands/<name>.md`. */
|
|
17
17
|
export const CURSOR_COMMAND_PATH = '.cursor/commands/reticle.md';
|
|
18
|
+
/**
|
|
19
|
+
* The frontmatter line that marks a command file as OURS.
|
|
20
|
+
*
|
|
21
|
+
* A stale command has to be refreshable — a file frozen at whatever release created it can never be
|
|
22
|
+
* improved for anyone who already ran init — but the path is one a human may have claimed for their
|
|
23
|
+
* own `/reticle`, and overwriting that would be destroying somebody's work. This line is how the two
|
|
24
|
+
* are told apart.
|
|
25
|
+
*/
|
|
26
|
+
export const SLASH_COMMAND_SIGNATURE = 'description: Verify this app in the browser with Reticle';
|
|
18
27
|
export const SLASH_COMMAND_BODY = `---
|
|
19
|
-
|
|
28
|
+
${SLASH_COMMAND_SIGNATURE} — drive one real flow and report what happened.
|
|
20
29
|
---
|
|
21
30
|
|
|
22
31
|
Verify this running app with Reticle. Drive it; do not read the code and guess.
|
package/dist/init/snippets.d.ts
CHANGED
|
@@ -15,19 +15,16 @@ export declare function nextConfigManual(configFile: string): string;
|
|
|
15
15
|
* setup ended at `bridge refused the connection: authentication failed` and no session ever appeared.
|
|
16
16
|
*/
|
|
17
17
|
export declare function nextReticleDevFile(port: number | undefined, projectId?: string, testids?: readonly string[], stores?: readonly string[]): string;
|
|
18
|
+
export declare function astroManual(port: number | undefined, projectId?: string,
|
|
18
19
|
/**
|
|
19
|
-
*
|
|
20
|
+
* A layout file that actually exists, when one does.
|
|
20
21
|
*
|
|
21
|
-
*
|
|
22
|
-
* `
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
* Astro bundles a page `<script>`, so the bare import resolves there. The token has to be inlined by
|
|
27
|
-
* the config because there is no plugin in the page's path to inject it, and `build.target` has to be
|
|
28
|
-
* raised or Astro down-levels the modern SDK bundle and dies on a destructuring transform.
|
|
22
|
+
* Reported from the field: on `examples/framework-react` — which has no layout at all, only
|
|
23
|
+
* `src/pages/index.astro` — init printed thirty lines telling the user to paste into "your
|
|
24
|
+
* layout". Instructions that name a file the project does not have read as a mistake by the
|
|
25
|
+
* reader, and cost them the time it takes to go and confirm it is missing.
|
|
29
26
|
*/
|
|
30
|
-
|
|
27
|
+
layoutPath?: string): string;
|
|
31
28
|
/**
|
|
32
29
|
* The app-side dev module the Vite plugin imports by convention.
|
|
33
30
|
*
|
package/dist/init/snippets.js
CHANGED
|
@@ -47,9 +47,9 @@ export function nextConfigManual(configFile) {
|
|
|
47
47
|
*/
|
|
48
48
|
export function nextReticleDevFile(port, projectId, testids = [], stores = []) {
|
|
49
49
|
const base = connectArg(port, projectId);
|
|
50
|
-
const fields =
|
|
50
|
+
const fields = '' === base ? '' : `${base.slice(1, -1).trim()}, `;
|
|
51
51
|
const ids = testids.map((t) => `'${t}'`).join(', ');
|
|
52
|
-
const storeBlock = stores.length
|
|
52
|
+
const storeBlock = 0 === stores.length
|
|
53
53
|
? ' // No state library detected. If you add one, register it here — see docs/usage.md.'
|
|
54
54
|
: stores.map((h) => ` // import your store, then: ${h}`).join('\n');
|
|
55
55
|
return `'use client';
|
|
@@ -73,7 +73,7 @@ export function ReticleDev() {
|
|
|
73
73
|
// form wires \`subscribe\` too, so every mutation emits a diff; the getter form is read-only.
|
|
74
74
|
${storeBlock}
|
|
75
75
|
registerCapabilities({
|
|
76
|
-
testids: [${ids}],${testids.length
|
|
76
|
+
testids: [${ids}],${0 === testids.length ? ' // none found — add data-testid to your key elements' : ''}
|
|
77
77
|
signals: [], // names you pass to reticle.signal()
|
|
78
78
|
stores: [], // the keys you registered above
|
|
79
79
|
});
|
|
@@ -95,8 +95,28 @@ ${storeBlock}
|
|
|
95
95
|
* the config because there is no plugin in the page's path to inject it, and `build.target` has to be
|
|
96
96
|
* raised or Astro down-levels the modern SDK bundle and dies on a destructuring transform.
|
|
97
97
|
*/
|
|
98
|
-
|
|
99
|
-
|
|
98
|
+
/**
|
|
99
|
+
* Name the file the connect <script> should go in — the layout when one exists, otherwise the page,
|
|
100
|
+
* because a project with no layout has nowhere else to put it and should not be told otherwise.
|
|
101
|
+
*/
|
|
102
|
+
function layoutHost(layoutPath) {
|
|
103
|
+
return layoutPath === undefined
|
|
104
|
+
? '2. This project has no layout, so put it in the page you want instrumented (e.g.\n src/pages/index.astro) — every page you want a session from needs it — inside <body>:'
|
|
105
|
+
: `2. In ${layoutPath} (or any other page you want instrumented), inside <body>:`;
|
|
106
|
+
}
|
|
107
|
+
export function astroManual(port, projectId,
|
|
108
|
+
/**
|
|
109
|
+
* A layout file that actually exists, when one does.
|
|
110
|
+
*
|
|
111
|
+
* Reported from the field: on `examples/framework-react` — which has no layout at all, only
|
|
112
|
+
* `src/pages/index.astro` — init printed thirty lines telling the user to paste into "your
|
|
113
|
+
* layout". Instructions that name a file the project does not have read as a mistake by the
|
|
114
|
+
* reader, and cost them the time it takes to go and confirm it is missing.
|
|
115
|
+
*/
|
|
116
|
+
layoutPath) {
|
|
117
|
+
const extra = port !== undefined && port !== RETICLE_DEFAULT_PORT
|
|
118
|
+
? `\n url: '${bridgeWsUrl(port)}',`
|
|
119
|
+
: '';
|
|
100
120
|
const id = projectId !== undefined && projectId.length > 0 ? `\n projectId: '${projectId}',` : '';
|
|
101
121
|
return `Astro renders its own HTML, so the connect goes in a page <script> and the pairing token is inlined by the config.
|
|
102
122
|
|
|
@@ -125,7 +145,7 @@ export function astroManual(port, projectId) {
|
|
|
125
145
|
},
|
|
126
146
|
});
|
|
127
147
|
|
|
128
|
-
|
|
148
|
+
${layoutHost(layoutPath)}
|
|
129
149
|
|
|
130
150
|
<script>
|
|
131
151
|
if (import.meta.env.DEV) {
|
|
@@ -153,7 +173,7 @@ Start the daemon BEFORE \`astro dev\`, so the token file exists when the config
|
|
|
153
173
|
*/
|
|
154
174
|
export function viteDevModuleFile(testids, stores) {
|
|
155
175
|
const ids = testids.map((t) => `'${t}'`).join(', ');
|
|
156
|
-
const storeBlock = stores.length
|
|
176
|
+
const storeBlock = 0 === stores.length
|
|
157
177
|
? ' // No state library detected. If you add one, register it here — see docs/usage.md.'
|
|
158
178
|
: stores.map((h) => ` // import your store, then: ${h}`).join('\n');
|
|
159
179
|
return `// Dev-only. Imported automatically by @reticlehq/vite-plugin — you do not need to import it.
|
|
@@ -173,7 +193,7 @@ if (import.meta.env.DEV) {
|
|
|
173
193
|
${storeBlock}
|
|
174
194
|
|
|
175
195
|
registerCapabilities({
|
|
176
|
-
testids: [${ids}],${testids.length
|
|
196
|
+
testids: [${ids}],${0 === testids.length ? ' // none found — add data-testid to your key elements' : ''}
|
|
177
197
|
signals: [], // names you pass to reticle.signal()
|
|
178
198
|
stores: [], // the keys you registered above
|
|
179
199
|
});
|
|
@@ -203,10 +223,11 @@ export function htmlManual(port, projectId) {
|
|
|
203
223
|
• Bundled app (Create React App, webpack, Parcel, Vue/Svelte CLI, etc.) — add to your ENTRY module
|
|
204
224
|
(e.g. src/index.js or src/main.js), where '@reticlehq/react' resolves through your bundler:
|
|
205
225
|
|
|
206
|
-
if (location.hostname === 'localhost') {
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
226
|
+
if (window.location.hostname === 'localhost') {
|
|
227
|
+
void import('@reticlehq/react').then(({ reticle, install }) => {
|
|
228
|
+
install();
|
|
229
|
+
reticle.connect(${arg});
|
|
230
|
+
});
|
|
210
231
|
}
|
|
211
232
|
|
|
212
233
|
• Plain static HTML with no build step — the browser can't resolve the bare '@reticlehq/react' import, so
|
|
@@ -226,7 +247,7 @@ export const SVELTEKIT_HOOKS_PATH = 'src/hooks.client.ts';
|
|
|
226
247
|
* here is the one thing this project exists not to do.
|
|
227
248
|
*/
|
|
228
249
|
export function unverifiedUiLibraryNote(library) {
|
|
229
|
-
return `Detected a ${library} app. Reticle's DOM, network, console and state tools work here,
|
|
250
|
+
return `Detected a ${library} app. Reticle's DOM, network, console and state tools work here, and source file:line does too — the build plugin stamps data-reticle-source regardless of UI library (measured on preact and svelte). What @reticlehq/react adds and you will NOT get is React component identity: component names and component stacks. No CI gate covers ${library}. Gated today: Vite + React, Next.js, Remix, Astro. If something doesn't work, please open an issue.`;
|
|
230
251
|
}
|
|
231
252
|
export const UNVERIFIED_FRAMEWORK_NOTE = 'Reticle has no SvelteKit app and no CI gate for one, so this wiring is untested — it may work, but nothing proves it and nothing will tell us if it breaks. Supported and gated today: Vite + React, Next.js, Remix and Astro. If the hook does not register a session, please open an issue.';
|
|
232
253
|
/**
|
|
@@ -236,7 +257,7 @@ export const UNVERIFIED_FRAMEWORK_NOTE = 'Reticle has no SvelteKit app and no CI
|
|
|
236
257
|
*/
|
|
237
258
|
export function svelteKitHooksFile(port, projectId) {
|
|
238
259
|
const base = connectArg(port, projectId);
|
|
239
|
-
const fields =
|
|
260
|
+
const fields = '' === base ? '' : `${base.slice(1, -1).trim()}, `;
|
|
240
261
|
return `// Dev-only: connect Reticle on the client. SvelteKit renders via app.html, so the Vite-plugin
|
|
241
262
|
// index.html injection doesn't fire — connect from this client hook instead.
|
|
242
263
|
if (import.meta.env.DEV) {
|
|
@@ -246,11 +267,19 @@ if (import.meta.env.DEV) {
|
|
|
246
267
|
// file it lives in, so @reticlehq/vite-plugin inlines it here at build time. Without it the
|
|
247
268
|
// console reads "bridge refused the connection: authentication failed" and no session appears.
|
|
248
269
|
const token = typeof __RETICLE_TOKEN__ !== 'undefined' ? __RETICLE_TOKEN__ : '';
|
|
249
|
-
|
|
270
|
+
const root = typeof __RETICLE_ROOT__ !== 'undefined' ? __RETICLE_ROOT__ : '';
|
|
271
|
+
const sdkVersion = typeof __RETICLE_SDK_VERSION__ !== 'undefined' ? __RETICLE_SDK_VERSION__ : '';
|
|
272
|
+
reticle.connect({
|
|
273
|
+
${fields}...(token.length > 0 ? { token } : {}),
|
|
274
|
+
...(root.length > 0 ? { root } : {}),
|
|
275
|
+
...(sdkVersion.length > 0 ? { sdkVersion } : {}),
|
|
276
|
+
});
|
|
250
277
|
});
|
|
251
278
|
}
|
|
252
279
|
|
|
253
280
|
declare const __RETICLE_TOKEN__: string | undefined;
|
|
281
|
+
declare const __RETICLE_ROOT__: string | undefined;
|
|
282
|
+
declare const __RETICLE_SDK_VERSION__: string | undefined;
|
|
254
283
|
`;
|
|
255
284
|
}
|
|
256
285
|
/**
|
package/dist/init/vite-config.js
CHANGED
|
@@ -37,7 +37,7 @@ function insertImport(source) {
|
|
|
37
37
|
function insertPlugin(source, port) {
|
|
38
38
|
return source.replace(PLUGINS_ARRAY, (match, _g, offset) => {
|
|
39
39
|
const next = source[offset + match.length] ?? '';
|
|
40
|
-
const separator =
|
|
40
|
+
const separator = '' === next || /\s/.test(next) ? '' : ' ';
|
|
41
41
|
return `${match}${reticlePluginCall(port)},${separator}`;
|
|
42
42
|
});
|
|
43
43
|
}
|