@reticlehq/server 2.4.0 → 2.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/SKILL.md +719 -0
- package/dist/bridge/auth-failure-reason.d.ts +19 -0
- package/dist/bridge/auth-failure-reason.js +29 -0
- package/dist/{bridge-security.d.ts → bridge/bridge-security.d.ts} +1 -1
- package/dist/{bridge-security.js → bridge/bridge-security.js} +1 -1
- package/dist/{bridge.d.ts → bridge/bridge.d.ts} +13 -1
- package/dist/{bridge.js → bridge/bridge.js} +95 -15
- package/dist/{bridge.test-harness.d.ts → bridge/bridge.test-harness.d.ts} +9 -11
- package/dist/{bridge.test-harness.js → bridge/bridge.test-harness.js} +18 -10
- package/dist/{pairing-token.js → bridge/pairing-token.js} +1 -1
- package/dist/capsule/capsule-store.d.ts +8 -3
- package/dist/capsule/capsule-store.js +7 -2
- package/dist/capsule/capsule.js +3 -3
- package/dist/capsule/causal-summary.d.ts +8 -0
- package/dist/capsule/causal-summary.js +22 -12
- package/dist/capsule/divergence.d.ts +9 -4
- package/dist/capsule/divergence.js +8 -8
- package/dist/capsule/predicate-to-links.js +11 -8
- package/dist/cli/cli-doctor.d.ts +1 -0
- package/dist/cli/cli-doctor.js +135 -0
- package/dist/{cli-flow-commands.d.ts → cli/cli-flow-commands.d.ts} +2 -2
- package/dist/{cli-flow-commands.js → cli/cli-flow-commands.js} +15 -15
- package/dist/{cli-launch.d.ts → cli/cli-launch.d.ts} +32 -3
- package/dist/cli/cli-launch.js +171 -0
- package/dist/cli/cli-parse-feedback.d.ts +31 -0
- package/dist/cli/cli-parse-feedback.js +56 -0
- package/dist/{cli-parse.d.ts → cli/cli-parse.d.ts} +6 -19
- package/dist/{cli-parse.js → cli/cli-parse.js} +118 -81
- package/dist/{cli-port.d.ts → cli/cli-port.d.ts} +12 -0
- package/dist/{cli-port.js → cli/cli-port.js} +9 -9
- package/dist/cli/cli-update-commands.d.ts +4 -0
- package/dist/cli/cli-update-commands.js +93 -0
- package/dist/{cli-verify.js → cli/cli-verify.js} +14 -14
- package/dist/{cloud-cli.js → cli/cloud-cli.js} +29 -24
- package/dist/cli/doctor-daemon-line.d.ts +37 -0
- package/dist/cli/doctor-daemon-line.js +35 -0
- package/dist/cli/port-holder.d.ts +43 -0
- package/dist/cli/port-holder.js +99 -0
- package/dist/cli.d.ts +2 -2
- package/dist/cli.js +302 -100
- package/dist/cloud/cloud-config.d.ts +0 -1
- package/dist/cloud/cloud-config.js +9 -9
- package/dist/cloud/cloud-sync.d.ts +1 -7
- package/dist/cloud/cloud-sync.js +12 -12
- package/dist/crawl/crawl-empty.d.ts +16 -0
- package/dist/crawl/crawl-empty.js +24 -0
- package/dist/crawl/crawl-tools.js +4 -1
- package/dist/crawl/crawl.js +26 -7
- package/dist/daemon/daemon-resilience.d.ts +73 -0
- package/dist/daemon/daemon-resilience.js +279 -0
- package/dist/daemon/daemon-usefulness.d.ts +61 -0
- package/dist/daemon/daemon-usefulness.js +62 -0
- package/dist/{daemon.d.ts → daemon/daemon.d.ts} +33 -8
- package/dist/{daemon.js → daemon/daemon.js} +105 -48
- package/dist/daemon/heartbeat.d.ts +115 -0
- package/dist/daemon/heartbeat.js +141 -0
- package/dist/daemon/idle-grace.d.ts +32 -0
- package/dist/daemon/idle-grace.js +41 -0
- package/dist/{idle-shutdown.d.ts → daemon/idle-shutdown.d.ts} +23 -2
- package/dist/{idle-shutdown.js → daemon/idle-shutdown.js} +37 -6
- package/dist/daemon/port-presence.d.ts +76 -0
- package/dist/daemon/port-presence.js +93 -0
- package/dist/daemon/state-dir.d.ts +7 -0
- package/dist/daemon/state-dir.js +59 -0
- package/dist/daemon/unclean-predecessor.d.ts +23 -0
- package/dist/daemon/unclean-predecessor.js +27 -0
- package/dist/domain/domain-model.js +2 -2
- package/dist/events/body-failures.js +8 -8
- package/dist/events/contradictions.d.ts +1 -1
- package/dist/events/contradictions.js +60 -10
- package/dist/events/echo-mismatch.js +10 -10
- package/dist/events/event-filters.js +6 -6
- package/dist/events/json-salvage.d.ts +2 -1
- package/dist/events/json-salvage.js +6 -6
- package/dist/events/observed-in-window.d.ts +12 -0
- package/dist/events/observed-in-window.js +21 -0
- package/dist/events/predicate-eval.d.ts +53 -20
- package/dist/events/predicate-eval.js +179 -55
- package/dist/events/predicate-parse.d.ts +23 -0
- package/dist/events/predicate-parse.js +54 -0
- package/dist/events/predicate.d.ts +7 -2
- package/dist/events/predicate.js +126 -35
- package/dist/events/reconcile.js +5 -5
- package/dist/events/ring-buffer.js +2 -17
- package/dist/events/stale-response.js +6 -6
- package/dist/events/testid-near-miss.d.ts +22 -0
- package/dist/events/testid-near-miss.js +33 -0
- package/dist/events/unit-mismatch.js +8 -8
- package/dist/flows/affected-tools.js +19 -4
- package/dist/flows/affected.js +1 -1
- package/dist/flows/annotate-target.d.ts +1 -0
- package/dist/flows/annotate-target.js +20 -0
- package/dist/flows/annotate-tools.js +25 -3
- package/dist/flows/annotate.js +17 -4
- package/dist/flows/assertion-integrity.d.ts +2 -1
- package/dist/flows/assertion-tiers-store.d.ts +3 -66
- package/dist/flows/assertion-tiers-store.js +2 -2
- package/dist/flows/attributed-failure.d.ts +17 -0
- package/dist/flows/attributed-failure.js +20 -0
- package/dist/flows/buddy-status.d.ts +2 -1
- package/dist/flows/buddy-status.js +2 -2
- package/dist/flows/coverage.d.ts +3 -2
- package/dist/flows/coverage.js +2 -2
- package/dist/flows/decision.js +24 -9
- package/dist/flows/empty-flow.d.ts +6 -0
- package/dist/flows/empty-flow.js +24 -0
- package/dist/flows/flake-store.d.ts +0 -1
- package/dist/flows/flake-store.js +1 -1
- package/dist/flows/flake.d.ts +2 -1
- package/dist/flows/flake.js +2 -2
- package/dist/flows/flow-classify.js +12 -0
- package/dist/flows/flow-replay-run.js +4 -4
- package/dist/flows/flow-replay.d.ts +6 -8
- package/dist/flows/flow-replay.js +132 -48
- package/dist/flows/flow-report.js +1 -1
- package/dist/flows/flow-scope.d.ts +1 -6
- package/dist/flows/flow-step-runners.d.ts +26 -6
- package/dist/flows/flow-step-runners.js +156 -6
- package/dist/flows/flow-success.js +20 -15
- package/dist/flows/flow-tools.js +63 -21
- package/dist/flows/flows.js +15 -11
- package/dist/flows/gate.d.ts +4 -3
- package/dist/flows/gate.js +1 -1
- package/dist/flows/heal-run.d.ts +0 -8
- package/dist/flows/heal-run.js +5 -5
- package/dist/flows/heal.js +3 -3
- package/dist/flows/predicate-to-expect.d.ts +38 -0
- package/dist/flows/predicate-to-expect.js +95 -0
- package/dist/flows/replay.d.ts +16 -1
- package/dist/flows/replay.js +58 -22
- package/dist/flows/role-anchor-nearest.d.ts +6 -0
- package/dist/flows/role-anchor-nearest.js +45 -0
- package/dist/flows/role-drift-reason.d.ts +20 -0
- package/dist/flows/role-drift-reason.js +39 -0
- package/dist/flows/server-verify.d.ts +1 -1
- package/dist/flows/server-verify.js +5 -5
- package/dist/flows/suite-flakes.d.ts +33 -0
- package/dist/flows/suite-flakes.js +32 -0
- package/dist/flows/verify-change-tools.js +32 -10
- package/dist/flows/verify-change-verdict.d.ts +15 -0
- package/dist/flows/verify-change-verdict.js +25 -0
- package/dist/flows/watch-batcher.d.ts +3 -2
- package/dist/honesty/already-true.d.ts +2 -0
- package/dist/honesty/already-true.js +31 -0
- package/dist/honesty/blind-spots.d.ts +35 -3
- package/dist/honesty/blind-spots.js +57 -12
- package/dist/honesty/honesty.d.ts +3 -2
- package/dist/honesty/honesty.js +4 -4
- package/dist/honesty/uncaptured-bodies.js +1 -1
- package/dist/honesty/unread-outcome.js +2 -2
- package/dist/honesty/verified.d.ts +31 -3
- package/dist/honesty/verified.js +64 -8
- package/dist/http-server.js +23 -7
- package/dist/hunt/hunt-report.d.ts +2 -1
- package/dist/hunt/hunt-report.js +2 -2
- package/dist/index.d.ts +9 -6
- package/dist/index.js +41 -25
- package/dist/init/agent-rules.d.ts +20 -1
- package/dist/init/agent-rules.js +64 -8
- package/dist/init/app-choice.d.ts +20 -0
- package/dist/init/app-choice.js +25 -0
- package/dist/init/astro-host.d.ts +21 -0
- package/dist/init/astro-host.js +27 -0
- package/dist/init/astro-patch.d.ts +16 -0
- package/dist/init/astro-patch.js +240 -0
- package/dist/init/capabilities.js +4 -1
- package/dist/init/cra.d.ts +50 -0
- package/dist/init/cra.js +106 -0
- package/dist/init/cursor.js +39 -4
- package/dist/init/desktop-doctor.d.ts +3 -2
- package/dist/init/desktop-doctor.js +4 -4
- package/dist/init/detect.d.ts +9 -2
- package/dist/init/detect.js +24 -5
- package/dist/init/mcp-clients.d.ts +81 -0
- package/dist/init/mcp-clients.js +248 -0
- package/dist/init/mcp-registered.d.ts +12 -0
- package/dist/init/mcp-registered.js +14 -0
- package/dist/init/mcp.d.ts +27 -2
- package/dist/init/mcp.js +40 -4
- package/dist/init/next-patch.d.ts +2 -4
- package/dist/init/next-patch.js +109 -19
- package/dist/init/node-io.js +29 -6
- package/dist/init/plan-framework.d.ts +16 -1
- package/dist/init/plan-framework.js +166 -8
- package/dist/init/plan.d.ts +85 -6
- package/dist/init/plan.js +196 -33
- package/dist/init/project-id.d.ts +5 -9
- package/dist/init/project-id.js +10 -22
- package/dist/init/run.d.ts +23 -1
- package/dist/init/run.js +338 -47
- package/dist/init/slash-command.d.ts +9 -0
- package/dist/init/slash-command.js +10 -1
- package/dist/init/snippets.d.ts +7 -10
- package/dist/init/snippets.js +44 -15
- package/dist/init/vite-config.js +1 -1
- package/dist/init/windows-quote.d.ts +38 -0
- package/dist/init/windows-quote.js +70 -0
- package/dist/init/workspace-apps.d.ts +23 -0
- package/dist/init/workspace-apps.js +68 -0
- package/dist/input/driven-redaction.js +1 -1
- package/dist/input/network-detail.js +8 -8
- package/dist/input/network-mock-tools.js +2 -2
- package/dist/input/network-mock.js +4 -4
- package/dist/input/real-input.js +8 -3
- package/dist/input/scroll-find.js +2 -2
- package/dist/input/scroll-tools.d.ts +0 -5
- package/dist/input/scroll-tools.js +11 -4
- package/dist/input/viewport-tools.js +2 -2
- package/dist/journal/ambient-store.d.ts +0 -2
- package/dist/journal/ambient-store.js +1 -1
- package/dist/journal/ambient.js +1 -1
- package/dist/journal/attach-journal.d.ts +2 -1
- package/dist/journal/deviation-report.js +6 -6
- package/dist/journal/deviation-service.js +1 -1
- package/dist/journal/envelope-store.d.ts +0 -2
- package/dist/journal/envelope-store.js +1 -1
- package/dist/journal/envelope.d.ts +2 -3
- package/dist/journal/envelope.js +3 -3
- package/dist/journal/journal-query.js +1 -1
- package/dist/journal/journal-recorder.d.ts +2 -1
- package/dist/journal/journal-recorder.js +2 -2
- package/dist/journal/rollups.js +9 -9
- package/dist/journal/session-end.d.ts +2 -1
- package/dist/journal/session-journal.js +4 -4
- package/dist/license/license.d.ts +0 -14
- package/dist/license/license.js +8 -8
- package/dist/log.d.ts +10 -0
- package/dist/log.js +11 -1
- package/dist/mcp/mcp-is-error.d.ts +13 -0
- package/dist/mcp/mcp-is-error.js +18 -0
- package/dist/mcp/mcp-outage.d.ts +32 -0
- package/dist/mcp/mcp-outage.js +55 -0
- package/dist/mcp/mcp-proxy.d.ts +134 -0
- package/dist/mcp/mcp-proxy.js +720 -0
- package/dist/{mcp.d.ts → mcp/mcp.d.ts} +13 -10
- package/dist/{mcp.js → mcp/mcp.js} +139 -37
- package/dist/mcp/proxy-handshake.d.ts +3 -0
- package/dist/mcp/proxy-handshake.js +54 -0
- package/dist/mcp/proxy-lifecycle.d.ts +64 -0
- package/dist/mcp/proxy-lifecycle.js +70 -0
- package/dist/mcp/tool-catalog-cache.d.ts +29 -0
- package/dist/mcp/tool-catalog-cache.js +55 -0
- package/dist/oracles/propose-consequences.d.ts +2 -1
- package/dist/oracles/propose-consequences.js +16 -11
- package/dist/oracles/self-instrument.d.ts +2 -1
- package/dist/oracles/self-instrument.js +1 -1
- package/dist/phenomena/phenomena.js +3 -3
- package/dist/platform.d.ts +16 -0
- package/dist/platform.js +16 -0
- package/dist/pool/browser-pool.d.ts +9 -0
- package/dist/pool/browser-pool.js +38 -9
- package/dist/pool/playwright-launcher.d.ts +15 -2
- package/dist/pool/playwright-launcher.js +21 -2
- package/dist/project/fs-port.js +1 -1
- package/dist/project/project-tools.js +2 -2
- package/dist/runs/build-verification-run.js +1 -1
- package/dist/runs/repair-prompt.d.ts +0 -6
- package/dist/runs/repair-prompt.js +3 -3
- package/dist/runs/risk-classify.js +1 -1
- package/dist/runs/run-diff.d.ts +0 -2
- package/dist/runs/run-diff.js +3 -3
- package/dist/runs/run-tools.js +2 -2
- package/dist/runs/verification-sync.js +2 -2
- package/dist/runs/verify-http.js +1 -1
- package/dist/runs/verify-server.js +1 -1
- package/dist/session/attachment-history.d.ts +46 -0
- package/dist/session/attachment-history.js +65 -0
- package/dist/session/command-timeout.d.ts +11 -3
- package/dist/session/command-timeout.js +2 -2
- package/dist/session/control-envelope.d.ts +6 -0
- package/dist/session/control-envelope.js +7 -1
- package/dist/session/dev-server-probe.d.ts +13 -0
- package/dist/session/dev-server-probe.js +52 -0
- package/dist/session/last-act.d.ts +17 -3
- package/dist/session/last-act.js +16 -4
- package/dist/session/live-control-tools.js +7 -2
- package/dist/session/live-control.js +1 -1
- package/dist/session/looks-like-dev-server.d.ts +14 -0
- package/dist/session/looks-like-dev-server.js +25 -0
- package/dist/session/no-session-diagnosis.d.ts +45 -0
- package/dist/session/no-session-diagnosis.js +120 -0
- package/dist/session/no-session-watch.d.ts +21 -0
- package/dist/session/no-session-watch.js +69 -0
- package/dist/session/observed-state.d.ts +4 -0
- package/dist/session/observed-state.js +19 -1
- package/dist/session/output-budget.js +1 -1
- package/dist/session/pending-commands.js +1 -0
- package/dist/session/session-health.d.ts +8 -1
- package/dist/session/session-health.js +8 -7
- package/dist/session/session-info.d.ts +43 -8
- package/dist/session/session-info.js +46 -1
- package/dist/session/session-lease.d.ts +0 -11
- package/dist/session/session-lease.js +1 -1
- package/dist/session/session-manager.d.ts +6 -0
- package/dist/session/session-manager.js +124 -12
- package/dist/session/session-readiness.d.ts +1 -1
- package/dist/session/session-readiness.js +6 -1
- package/dist/session/session-reaper.js +1 -1
- package/dist/session/session-reconnect.d.ts +32 -0
- package/dist/session/session-reconnect.js +42 -0
- package/dist/session/session.d.ts +15 -2
- package/dist/session/session.js +77 -26
- package/dist/session/state-select.d.ts +1 -1
- package/dist/session/state-select.js +1 -1
- package/dist/status-payload.d.ts +19 -0
- package/dist/status-payload.js +11 -0
- package/dist/telemetry/argument-shape.js +10 -1
- package/dist/telemetry/browser-mode.d.ts +27 -0
- package/dist/telemetry/browser-mode.js +33 -0
- package/dist/telemetry/bug-found.d.ts +17 -21
- package/dist/telemetry/bug-found.js +120 -8
- package/dist/telemetry/cli-telemetry.d.ts +2 -12
- package/dist/telemetry/cli-telemetry.js +27 -4
- package/dist/telemetry/crash-cause.d.ts +47 -0
- package/dist/telemetry/crash-cause.js +104 -0
- package/dist/telemetry/daemon-telemetry.d.ts +38 -1
- package/dist/telemetry/daemon-telemetry.js +37 -12
- package/dist/telemetry/dev-repo.js +4 -4
- package/dist/telemetry/error-class.d.ts +26 -0
- package/dist/telemetry/error-class.js +52 -0
- package/dist/telemetry/error-fingerprint.js +21 -2
- package/dist/telemetry/feedback-cli.d.ts +8 -3
- package/dist/telemetry/feedback-cli.js +52 -8
- package/dist/telemetry/feedback-context.d.ts +23 -6
- package/dist/telemetry/feedback-context.js +105 -5
- package/dist/telemetry/feedback-delivery.d.ts +20 -0
- package/dist/telemetry/feedback-delivery.js +30 -0
- package/dist/telemetry/feedback-outbox.d.ts +22 -0
- package/dist/telemetry/feedback-outbox.js +87 -0
- package/dist/telemetry/feedback.d.ts +22 -1
- package/dist/telemetry/feedback.js +67 -8
- package/dist/telemetry/git-facts.d.ts +7 -0
- package/dist/telemetry/git-facts.js +5 -5
- package/dist/telemetry/project-profile.js +2 -1
- package/dist/telemetry/session-metrics.d.ts +47 -3
- package/dist/telemetry/session-metrics.js +315 -15
- package/dist/telemetry/telemetry.d.ts +45 -2
- package/dist/telemetry/telemetry.js +150 -37
- package/dist/telemetry/verification-of.d.ts +22 -0
- package/dist/telemetry/verification-of.js +84 -0
- package/dist/tools/act-capsule.js +2 -2
- package/dist/tools/act-danger.d.ts +12 -0
- package/dist/tools/act-danger.js +20 -3
- package/dist/tools/act-helpers.js +4 -4
- package/dist/tools/act-tools.d.ts +0 -8
- package/dist/tools/act-tools.js +114 -118
- package/dist/tools/act-view.js +7 -7
- package/dist/tools/alias-args.d.ts +17 -0
- package/dist/tools/alias-args.js +25 -0
- package/dist/tools/assert-grade.js +9 -9
- package/dist/tools/assert-verdict.d.ts +9 -1
- package/dist/tools/assert-verdict.js +21 -6
- package/dist/tools/browser-tools.js +36 -11
- package/dist/tools/contract-tools.d.ts +0 -5
- package/dist/tools/contract-tools.js +27 -1
- package/dist/tools/coverage-identity.d.ts +34 -0
- package/dist/tools/coverage-identity.js +46 -0
- package/dist/tools/coverage-tools.js +10 -14
- package/dist/tools/dynamic-tools.d.ts +2 -1
- package/dist/tools/dynamic-tools.js +96 -19
- package/dist/tools/error-recovery.d.ts +34 -2
- package/dist/tools/error-recovery.js +255 -4
- package/dist/tools/feedback-invite.d.ts +48 -0
- package/dist/tools/feedback-invite.js +71 -0
- package/dist/tools/feedback-tools.d.ts +12 -1
- package/dist/tools/feedback-tools.js +28 -5
- package/dist/tools/invoke-tool.d.ts +0 -12
- package/dist/tools/invoke-tool.js +126 -33
- package/dist/tools/lease-hint.d.ts +17 -0
- package/dist/tools/lease-hint.js +24 -0
- package/dist/tools/lease-tools.js +6 -5
- package/dist/tools/merge-tools.d.ts +10 -1
- package/dist/tools/merge-tools.js +9 -3
- package/dist/tools/merged-name-redirect.d.ts +13 -0
- package/dist/tools/merged-name-redirect.js +55 -0
- package/dist/tools/navigate-arrival.d.ts +26 -0
- package/dist/tools/navigate-arrival.js +60 -0
- package/dist/tools/navigate-result.d.ts +31 -0
- package/dist/tools/navigate-result.js +38 -0
- package/dist/tools/observe-tools.js +72 -20
- package/dist/tools/observed-nothing.d.ts +25 -0
- package/dist/tools/observed-nothing.js +33 -0
- package/dist/tools/query-paginate.js +2 -2
- package/dist/tools/query-shape.d.ts +18 -0
- package/dist/tools/query-shape.js +39 -0
- package/dist/tools/read-tools.js +81 -20
- package/dist/tools/real-input-attempt.d.ts +28 -0
- package/dist/tools/real-input-attempt.js +93 -0
- package/dist/tools/reconcile-tools.js +7 -7
- package/dist/tools/reload-result.d.ts +14 -0
- package/dist/tools/reload-result.js +29 -0
- package/dist/tools/snapshot-delta.d.ts +4 -5
- package/dist/tools/snapshot-delta.js +0 -0
- package/dist/tools/tool-kit.d.ts +16 -0
- package/dist/tools/tool-surface.d.ts +83 -0
- package/dist/tools/tool-surface.js +217 -0
- package/dist/tools/tools-helpers.js +6 -6
- package/dist/tools/tools.d.ts +15 -0
- package/dist/tools/tools.js +53 -17
- package/dist/tools/unadvertised-help.d.ts +5 -0
- package/dist/tools/unadvertised-help.js +39 -0
- package/dist/trace.d.ts +69 -0
- package/dist/trace.js +150 -0
- package/dist/update/nudge-credit.d.ts +4 -0
- package/dist/update/nudge-credit.js +53 -0
- package/dist/update/sdk-sync.d.ts +21 -0
- package/dist/update/sdk-sync.js +42 -0
- package/dist/update/update-checker.js +3 -3
- package/dist/update/update-nudge.d.ts +63 -6
- package/dist/update/update-nudge.js +128 -15
- package/dist/update/updater.d.ts +1 -9
- package/dist/update/updater.js +13 -8
- package/dist/version/peer-announce.d.ts +4 -0
- package/dist/version/peer-announce.js +30 -0
- package/dist/{server-version.js → version/server-version.js} +5 -1
- package/dist/version/version-nudge.d.ts +28 -0
- package/dist/version/version-nudge.js +40 -0
- package/dist/version/version-skew.d.ts +66 -0
- package/dist/version/version-skew.js +71 -0
- package/dist/visual/visual-diff.js +2 -2
- package/dist/visual/visual-tools.js +4 -4
- package/docs/README.md +55 -0
- package/docs/agent-cheatsheet.md +85 -0
- package/docs/architecture.md +113 -0
- package/docs/benchmarks.md +255 -0
- package/docs/debugging.md +102 -0
- package/docs/deploy-checks.md +69 -0
- package/docs/desktop-apps.md +256 -0
- package/docs/enterprise.md +62 -0
- package/docs/fixtures-dispatch-receiver.yml +76 -0
- package/docs/fixtures.md +45 -0
- package/docs/flows.md +166 -0
- package/docs/gate-plan.md +98 -0
- package/docs/gates.md +117 -0
- package/docs/getting-started.md +469 -0
- package/docs/human-control.md +63 -0
- package/docs/integration-patterns.md +171 -0
- package/docs/local-registry.md +126 -0
- package/docs/matrix/2.5.0/claude-code-machine.json +19 -0
- package/docs/matrix/2.5.0/codex-machine.json +20 -0
- package/docs/matrix/2.5.0/cursor-machine.json +22 -0
- package/docs/matrix/2.5.0/gemini-machine.json +22 -0
- package/docs/matrix/2.5.0/opencode-machine.json +22 -0
- package/docs/matrix/2.5.0/vscode-machine.json +22 -0
- package/docs/matrix/2.5.0/windsurf-machine.json +22 -0
- package/docs/matrix/MATRIX.md +26 -0
- package/docs/matrix/README.md +59 -0
- package/docs/mint.json +61 -0
- package/docs/multi-agent-testing.md +55 -0
- package/docs/platform-integration.md +122 -0
- package/docs/system-map.md +152 -0
- package/docs/telemetry-contract.md +214 -0
- package/docs/telemetry.md +191 -0
- package/docs/testing.md +82 -0
- package/docs/token-efficiency.md +80 -0
- package/docs/usage.md +1046 -0
- package/package.json +8 -5
- package/dist/cli-doctor.d.ts +0 -6
- package/dist/cli-doctor.js +0 -60
- package/dist/cli-launch.js +0 -112
- package/dist/daemon-resilience.d.ts +0 -22
- package/dist/daemon-resilience.js +0 -85
- package/dist/mcp-proxy.d.ts +0 -67
- package/dist/mcp-proxy.js +0 -337
- package/dist/tools/profiles.d.ts +0 -37
- package/dist/tools/profiles.js +0 -139
- /package/dist/{pairing-token.d.ts → bridge/pairing-token.d.ts} +0 -0
- /package/dist/{token-auth.d.ts → bridge/token-auth.d.ts} +0 -0
- /package/dist/{token-auth.js → bridge/token-auth.js} +0 -0
- /package/dist/{cli-verify.d.ts → cli/cli-verify.d.ts} +0 -0
- /package/dist/{cloud-cli.d.ts → cli/cloud-cli.d.ts} +0 -0
- /package/dist/{server-version.d.ts → version/server-version.d.ts} +0 -0
package/dist/init/run.js
CHANGED
|
@@ -4,16 +4,52 @@
|
|
|
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 {
|
|
8
|
-
import {
|
|
7
|
+
import { spanSync } from '../trace.js';
|
|
8
|
+
import { readFileSync } from 'node:fs';
|
|
9
|
+
import { homedir } from 'node:os';
|
|
10
|
+
import { detect, Framework, namesAPackageManager } 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 { CLI, 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
|
+
}
|
|
43
|
+
import { DEPS_TARGET, frameworkPackages, MCP_TARGET, buildPlan, StepStatus, } from './plan.js';
|
|
9
44
|
import { claudeAvailableProbe, claudeExistsProbe } from './mcp.js';
|
|
10
45
|
import { reticleDevLocation } from './next-patch.js';
|
|
11
46
|
import { scanTestids, storeHints } from './capabilities.js';
|
|
12
47
|
import { CURSOR_DIR_RELPATH, CURSOR_MCP_RELPATH } from './cursor.js';
|
|
48
|
+
import { fileBackedClients, clientMarkerRelPath, ConfigScope, McpClient } from './mcp-clients.js';
|
|
13
49
|
import { deriveProjectId, packageName } from './project-id.js';
|
|
14
50
|
import { VITE_DEV_MODULE_PATH } from './snippets.js';
|
|
15
51
|
import { CLAUDE_COMMAND_PATH, CURSOR_COMMAND_PATH } from './slash-command.js';
|
|
16
|
-
import { SERVER_VERSION } from '../server-version.js';
|
|
52
|
+
import { SERVER_VERSION } from '../version/server-version.js';
|
|
17
53
|
import { InitFailure, reportInitOutcome } from '../telemetry/init-telemetry.js';
|
|
18
54
|
/** Lockfile basenames, in package-manager preference order (mirrors detect.ts). */
|
|
19
55
|
const LOCKFILE_NAMES = [
|
|
@@ -27,11 +63,20 @@ const LOCKFILE_NAMES = [
|
|
|
27
63
|
* Resolve the lockfiles set used to pick the package manager. A lockfile in the project root wins;
|
|
28
64
|
* otherwise we walk UP the directory tree (monorepos keep the lockfile at the workspace root, not in
|
|
29
65
|
* each package) so `reticle init` in a sub-package suggests `pnpm add` instead of defaulting to `npm i`.
|
|
66
|
+
*
|
|
67
|
+
* The walk is skipped when the project has its own installed tree, because an INHERITED lockfile is
|
|
68
|
+
* weaker evidence than a `node_modules` sitting right there — the ancestor describes the workspace,
|
|
69
|
+
* the tree describes THIS package. Reported from the field: `init` in a `frontend/` app installed
|
|
70
|
+
* with npm emitted `pnpm add -D` off a repo-root `pnpm-lock.yaml`, pnpm was not on PATH, and the
|
|
71
|
+
* failed install took every downstream wiring step with it. A LOCAL lockfile still wins over the
|
|
72
|
+
* tree — it is a deliberate statement about this package, not an inheritance.
|
|
30
73
|
*/
|
|
31
|
-
export function resolveLockfiles(rootFiles, cwd, io) {
|
|
74
|
+
export function resolveLockfiles(rootFiles, cwd, io, nodeModulesMarkers = new Set()) {
|
|
32
75
|
const set = new Set(rootFiles);
|
|
33
76
|
if (LOCKFILE_NAMES.some((name) => set.has(name)))
|
|
34
77
|
return set; // local lockfile is authoritative
|
|
78
|
+
if (namesAPackageManager(nodeModulesMarkers))
|
|
79
|
+
return set;
|
|
35
80
|
let dir = cwd;
|
|
36
81
|
for (let depth = 0; depth < 50; depth++) {
|
|
37
82
|
for (const name of LOCKFILE_NAMES) {
|
|
@@ -48,6 +93,7 @@ export function resolveLockfiles(rootFiles, cwd, io) {
|
|
|
48
93
|
return set;
|
|
49
94
|
}
|
|
50
95
|
const PACKAGE_JSON = 'package.json';
|
|
96
|
+
const NODE_MODULES_DIR = 'node_modules';
|
|
51
97
|
/**
|
|
52
98
|
* Root-layout candidates, App Router only. `--src-dir` apps keep theirs under `src/app`, and the
|
|
53
99
|
* ReticleDev component has to land NEXT TO the layout or the relative import it generates is dead.
|
|
@@ -108,6 +154,16 @@ const VITE_CONFIG_CANDIDATES = [
|
|
|
108
154
|
'vite.config.mjs',
|
|
109
155
|
'vite.config.mts',
|
|
110
156
|
];
|
|
157
|
+
const ASTRO_CONFIG_CANDIDATES = [
|
|
158
|
+
'astro.config.mjs',
|
|
159
|
+
'astro.config.js',
|
|
160
|
+
'astro.config.ts',
|
|
161
|
+
'astro.config.cjs',
|
|
162
|
+
];
|
|
163
|
+
/** Where a conventional Astro app keeps the layout every page wraps itself in. */
|
|
164
|
+
const ASTRO_LAYOUTS_DIR = 'src/layouts';
|
|
165
|
+
/** Also searched: an app with no layouts directory renders the document straight from a page. */
|
|
166
|
+
const ASTRO_PAGES_DIR = 'src/pages';
|
|
111
167
|
const NEXT_CONFIG_CANDIDATES = [
|
|
112
168
|
'next.config.mjs',
|
|
113
169
|
'next.config.js',
|
|
@@ -119,6 +175,21 @@ const NEXT_CONFIG_CANDIDATES = [
|
|
|
119
175
|
* decide whether the install finished. A notice gets its own mark so "steps remaining" can reach zero
|
|
120
176
|
* on a working install that happens to be on an ungated stack.
|
|
121
177
|
*/
|
|
178
|
+
/**
|
|
179
|
+
* A step's status AFTER the run, which is what actually happened to it.
|
|
180
|
+
*
|
|
181
|
+
* `report` applies the same downgrade when printing: a step that failed or was skipped is shown as
|
|
182
|
+
* MANUAL whatever it planned to be. Reading the planned status alone would say a step applied when
|
|
183
|
+
* the run had already given up on it.
|
|
184
|
+
*/
|
|
185
|
+
function resolvedStatus(plan, target, failed, skipped) {
|
|
186
|
+
const step = plan.steps.find((s) => s.target === target);
|
|
187
|
+
if (step === undefined)
|
|
188
|
+
return undefined;
|
|
189
|
+
if (failed.has(target) || skipped.has(target))
|
|
190
|
+
return StepStatus.MANUAL;
|
|
191
|
+
return step.status;
|
|
192
|
+
}
|
|
122
193
|
const STATUS_SYMBOL = {
|
|
123
194
|
[StepStatus.APPLY]: '✓',
|
|
124
195
|
[StepStatus.MANUAL]: '⚠',
|
|
@@ -137,17 +208,19 @@ function gatherPlanInput(options, io, pkgRaw) {
|
|
|
137
208
|
// Stable identity derived from the app's package.json name + root, so it survives port changes.
|
|
138
209
|
const projectId = deriveProjectId(packageName(pkg), options.cwd);
|
|
139
210
|
const rootFiles = new Set(io.rootFiles());
|
|
211
|
+
const nodeModulesMarkers = new Set(io.listFiles('node_modules'));
|
|
140
212
|
const detectInput = {
|
|
141
|
-
pkg: typeof pkg
|
|
213
|
+
pkg: 'object' === typeof pkg && pkg !== null ? pkg : {},
|
|
142
214
|
configFiles: rootFiles,
|
|
143
|
-
// Walk up for the lockfile so a monorepo sub-package picks the workspace's package manager
|
|
144
|
-
|
|
215
|
+
// Walk up for the lockfile so a monorepo sub-package picks the workspace's package manager —
|
|
216
|
+
// unless this package's own installed tree already answers it, which outranks an inherited one.
|
|
217
|
+
lockfiles: resolveLockfiles(rootFiles, options.cwd, io, nodeModulesMarkers),
|
|
145
218
|
// An already-installed tree names its own manager, which matters when no lockfile is committed.
|
|
146
|
-
nodeModulesMarkers
|
|
219
|
+
nodeModulesMarkers,
|
|
147
220
|
};
|
|
148
221
|
const detection = detect(detectInput);
|
|
149
222
|
const vitePath = firstPresent(rootFiles, VITE_CONFIG_CANDIDATES);
|
|
150
|
-
const viteSource =
|
|
223
|
+
const viteSource = null === vitePath ? null : io.readFile(vitePath);
|
|
151
224
|
const viteConfig = vitePath !== null && viteSource !== null ? { path: vitePath, source: viteSource } : null;
|
|
152
225
|
// Global MCP registration targets each agent that's present: Claude via its CLI, Cursor via its
|
|
153
226
|
// global config file. Only probe when the MCP step is in play.
|
|
@@ -155,16 +228,57 @@ function gatherPlanInput(options, io, pkgRaw) {
|
|
|
155
228
|
const claudeCli = options.mcp ? io.probe(availableProbe.command, availableProbe.args) : false;
|
|
156
229
|
const existsProbe = claudeExistsProbe();
|
|
157
230
|
const mcpExists = claudeCli ? io.probe(existsProbe.command, existsProbe.args) : false;
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
const
|
|
231
|
+
// join(), not a literal '/': this path is checked and WRITTEN on the user's machine, and a
|
|
232
|
+
// backslash platform silently misses the config it is meant to find.
|
|
233
|
+
const cursorDir = join(io.homeDir(), CURSOR_DIR_RELPATH);
|
|
234
|
+
const cursorConfigPath = join(io.homeDir(), CURSOR_MCP_RELPATH);
|
|
235
|
+
// Cursor is "present" if its global config directory exists OR the project carries a `.cursor/`.
|
|
236
|
+
// The directory alone missed a real case: Cursor installed with a FRESH profile has not written
|
|
237
|
+
// ~/.cursor yet, so a user who had it open got no Cursor step and no message about one. A
|
|
238
|
+
// project-level .cursor/ is unambiguous evidence somebody uses it here.
|
|
239
|
+
const cursorPresent = options.mcp && (io.exists(cursorDir) || io.exists(CURSOR_DIR_RELPATH));
|
|
161
240
|
const cursorConfig = cursorPresent ? io.readFile(cursorConfigPath) : null;
|
|
241
|
+
// Every OTHER MCP client this machine shows evidence of. Conservative and one-directional: we
|
|
242
|
+
// write into a config a client ALREADY has, and never create ~/.gemini or ~/.codeium for somebody
|
|
243
|
+
// who does not use them. Cursor and Claude are handled above by their own steps.
|
|
244
|
+
const detectedClients = options.mcp
|
|
245
|
+
? fileBackedClients()
|
|
246
|
+
.filter((spec) => spec.id !== McpClient.CURSOR)
|
|
247
|
+
.map((spec) => {
|
|
248
|
+
const marker = clientMarkerRelPath(spec);
|
|
249
|
+
const absolute = spec.scope === ConfigScope.HOME ? join(io.homeDir(), spec.relPath) : spec.relPath;
|
|
250
|
+
const markerPath = spec.scope === ConfigScope.HOME ? join(io.homeDir(), marker) : marker;
|
|
251
|
+
if (!io.exists(markerPath))
|
|
252
|
+
return null;
|
|
253
|
+
return {
|
|
254
|
+
id: spec.id,
|
|
255
|
+
configPath: absolute,
|
|
256
|
+
// io.readFile passes absolute paths through unchanged, so one call covers both scopes.
|
|
257
|
+
existing: io.readFile(absolute),
|
|
258
|
+
};
|
|
259
|
+
})
|
|
260
|
+
.filter((entry) => entry !== null)
|
|
261
|
+
: [];
|
|
262
|
+
const astroPath = firstPresent(rootFiles, ASTRO_CONFIG_CANDIDATES);
|
|
263
|
+
const astroSource = null === astroPath ? null : io.readFile(astroPath);
|
|
264
|
+
// Which file owns the DOCUMENT, not how many files sit in a directory. The old rule ("exactly one
|
|
265
|
+
// .astro in src/layouts") fired on neither real Astro app: one has no layouts directory and
|
|
266
|
+
// renders from src/pages/index.astro, the other has three files there of which two are partials.
|
|
267
|
+
// `</body>` is the discriminator — see astro-host.
|
|
268
|
+
const astroCandidates = [ASTRO_LAYOUTS_DIR, ASTRO_PAGES_DIR].flatMap((dir) => io
|
|
269
|
+
.listFiles(dir)
|
|
270
|
+
.filter((f) => f.endsWith('.astro'))
|
|
271
|
+
.map((f) => ({ path: `${dir}/${f}`, source: io.readFile(`${dir}/${f}`) }))
|
|
272
|
+
.filter((c) => c.source !== null));
|
|
273
|
+
const astroHost = pickAstroHost(astroCandidates);
|
|
274
|
+
const layoutRelPath = astroHost?.path ?? null;
|
|
275
|
+
const astroLayoutSource = astroHost?.source ?? null;
|
|
162
276
|
const nextConfigFile = firstPresent(rootFiles, NEXT_CONFIG_CANDIDATES);
|
|
163
277
|
// 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))
|
|
278
|
+
const layoutPath = NEXT_LAYOUT_CANDIDATES.find((p) => io.exists(p)) ??
|
|
279
|
+
NEXT_PAGES_APP_CANDIDATES.find((p) => io.exists(p)) ??
|
|
166
280
|
null;
|
|
167
|
-
const layoutSource =
|
|
281
|
+
const layoutSource = null === layoutPath ? null : io.readFile(layoutPath);
|
|
168
282
|
// Where the component goes depends on WHICH router mounts it: `pages/` routes on presence, so a
|
|
169
283
|
// component there becomes a broken route; `app/` routes on filename, so a sibling is inert.
|
|
170
284
|
const devLocation = reticleDevLocation(layoutPath ?? 'app/layout.tsx', detection.typescript);
|
|
@@ -173,12 +287,17 @@ function gatherPlanInput(options, io, pkgRaw) {
|
|
|
173
287
|
claudeCli,
|
|
174
288
|
mcpExists,
|
|
175
289
|
cursorPresent,
|
|
290
|
+
detectedClients,
|
|
176
291
|
cursorProjectPresent: io.exists(CURSOR_DIR_RELPATH),
|
|
177
292
|
cursorConfig,
|
|
178
293
|
cursorConfigPath,
|
|
179
294
|
viteConfig,
|
|
295
|
+
astroConfig: astroPath !== null && astroSource !== null ? { path: astroPath, source: astroSource } : null,
|
|
296
|
+
astroLayout: layoutRelPath !== null && astroLayoutSource !== null
|
|
297
|
+
? { path: layoutRelPath, source: astroLayoutSource }
|
|
298
|
+
: null,
|
|
180
299
|
nextConfigFile,
|
|
181
|
-
nextConfigSource:
|
|
300
|
+
nextConfigSource: null === nextConfigFile ? null : io.readFile(nextConfigFile),
|
|
182
301
|
nextLayout: layoutPath !== null && layoutSource !== null
|
|
183
302
|
? { path: layoutPath, source: layoutSource }
|
|
184
303
|
: null,
|
|
@@ -190,13 +309,16 @@ function gatherPlanInput(options, io, pkgRaw) {
|
|
|
190
309
|
nextReticleDevImport: devLocation.importSpecifier,
|
|
191
310
|
nextReticleDevExists: io.exists(devLocation.path),
|
|
192
311
|
svelteKitHooksExists: io.exists(SVELTEKIT_HOOKS),
|
|
312
|
+
craEntry: craEntryOf(io),
|
|
313
|
+
craEnv: io.readFile(CRA_ENV_PATH),
|
|
314
|
+
pairingToken: readPairingToken(),
|
|
193
315
|
reticleConfigExists: io.exists('.reticle.json'),
|
|
194
316
|
// Read the agent instruction files so the rule merge stays idempotent across re-runs.
|
|
195
317
|
claudeMdContent: io.readFile('CLAUDE.md'),
|
|
196
318
|
agentsMdContent: io.readFile('AGENTS.md'),
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
319
|
+
cursorRuleContent: io.readFile(CURSOR_RULE_PATH),
|
|
320
|
+
claudeCommandContent: io.readFile(CLAUDE_COMMAND_PATH),
|
|
321
|
+
cursorCommandContent: io.readFile(CURSOR_COMMAND_PATH),
|
|
200
322
|
options: {
|
|
201
323
|
port: options.port,
|
|
202
324
|
mcp: options.mcp,
|
|
@@ -208,12 +330,13 @@ function gatherPlanInput(options, io, pkgRaw) {
|
|
|
208
330
|
};
|
|
209
331
|
}
|
|
210
332
|
/** Where workspace tooling conventionally puts packages. */
|
|
211
|
-
|
|
333
|
+
/** pnpm's workspace declaration, read when present — it is authoritative about where packages live. */
|
|
334
|
+
const PNPM_WORKSPACE = 'pnpm-workspace.yaml';
|
|
212
335
|
/** Deps that mark a directory as a runnable web app even when it has no bundler config file. */
|
|
213
336
|
const APP_DEPS = ['next', 'vite'];
|
|
214
337
|
function looksLikeApp(dir, io) {
|
|
215
338
|
const pkgRaw = io.readFile(`${dir}/${PACKAGE_JSON}`);
|
|
216
|
-
if (
|
|
339
|
+
if (null === pkgRaw)
|
|
217
340
|
return false;
|
|
218
341
|
const configs = [...VITE_CONFIG_CANDIDATES, ...NEXT_CONFIG_CANDIDATES];
|
|
219
342
|
if (configs.some((c) => io.exists(`${dir}/${c}`)))
|
|
@@ -231,15 +354,54 @@ function looksLikeApp(dir, io) {
|
|
|
231
354
|
*/
|
|
232
355
|
export function findWorkspaceApps(io) {
|
|
233
356
|
const found = [];
|
|
234
|
-
|
|
357
|
+
// A workspace DECLARES its packages; `['apps','packages']` was a guess that missed a real repo
|
|
358
|
+
// with three Next apps at web/, admin/ and space/ — and missing them meant init ran against the
|
|
359
|
+
// root and reported ✓ for a file Next never compiles. See workspace-apps.
|
|
360
|
+
const pkgRaw = io.readFile(PACKAGE_JSON);
|
|
361
|
+
let pkgWorkspaces;
|
|
362
|
+
try {
|
|
363
|
+
const parsed = null === pkgRaw ? undefined : JSON.parse(pkgRaw);
|
|
364
|
+
pkgWorkspaces =
|
|
365
|
+
'object' === typeof parsed && parsed !== null
|
|
366
|
+
? parsed.workspaces
|
|
367
|
+
: undefined;
|
|
368
|
+
}
|
|
369
|
+
catch {
|
|
370
|
+
pkgWorkspaces = undefined;
|
|
371
|
+
}
|
|
372
|
+
const parents = workspaceParents({
|
|
373
|
+
...(null === io.readFile(PNPM_WORKSPACE)
|
|
374
|
+
? {}
|
|
375
|
+
: { pnpmWorkspace: io.readFile(PNPM_WORKSPACE) ?? '' }),
|
|
376
|
+
...(pkgWorkspaces === undefined ? {} : { pkgWorkspaces }),
|
|
377
|
+
topLevelDirs: io.listDirs('.'),
|
|
378
|
+
});
|
|
379
|
+
// A declared parent can itself BE the app (`workspaces: ["web"]`), so check both the directory and
|
|
380
|
+
// its children rather than assuming one level of nesting.
|
|
381
|
+
for (const parent of parents) {
|
|
382
|
+
if (looksLikeApp(parent, io))
|
|
383
|
+
found.push(parent);
|
|
235
384
|
for (const name of io.listDirs(parent)) {
|
|
236
385
|
const dir = `${parent}/${name}`;
|
|
237
386
|
if (looksLikeApp(dir, io))
|
|
238
387
|
found.push(dir);
|
|
239
388
|
}
|
|
240
389
|
}
|
|
241
|
-
return found;
|
|
390
|
+
return [...new Set(found)];
|
|
242
391
|
}
|
|
392
|
+
/**
|
|
393
|
+
* The standing ask, printed at the end of every `init`.
|
|
394
|
+
*
|
|
395
|
+
* Addressed to the agent as much as the human: whichever of them just ran this command is the one
|
|
396
|
+
* holding the experience, and neither has a Reticle tool surface to file through at this point.
|
|
397
|
+
*
|
|
398
|
+
* Printed by the `runInit` wrapper, NOT by `report()` — see the comment there for why, and for how
|
|
399
|
+
* the workspace redirect is kept from asking twice.
|
|
400
|
+
*/
|
|
401
|
+
export const FEEDBACK_HINT = 'Anything wrong, missing, or awkward — in this setup or in Reticle itself? Tell us; it is the ' +
|
|
402
|
+
'one thing that decides what gets fixed:\n' +
|
|
403
|
+
` ${CLI} feedback --agent --kind <bug|gap|ambiguity|feature_request|improvement> "what happened" (agents)\n` +
|
|
404
|
+
` ${CLI} feedback "what worked, what didn't" (humans)`;
|
|
243
405
|
function restartHint(framework) {
|
|
244
406
|
if (framework === Framework.NEXT)
|
|
245
407
|
return 'Restart `next dev`, then ask your agent: "List Reticle sessions".';
|
|
@@ -253,13 +415,31 @@ function restartHint(framework) {
|
|
|
253
415
|
}
|
|
254
416
|
const SKIPPED_DETAIL = 'skipped — the dependency install above failed, and wiring the app to a package that is not ' +
|
|
255
417
|
'installed stops it booting. Run that install, then re-run `reticle init`.';
|
|
256
|
-
function report(plan, dryRun, failed, skipped, io) {
|
|
418
|
+
function report(plan, dryRun, failed, skipped, degraded, io, projectDir) {
|
|
257
419
|
io.print(dryRun ? 'reticle init (dry run — no files written)' : 'reticle init');
|
|
420
|
+
// Every path below is printed RELATIVE, and until now nothing said what to. Reported from the
|
|
421
|
+
// field as "[✓] Reticle config → .reticle.json" followed by the file not being there: the app was
|
|
422
|
+
// in `frontend/`, init redirected into it, and the report's `.reticle.json` was true about a
|
|
423
|
+
// directory the reader was not standing in. One line makes every path in the report unambiguous.
|
|
424
|
+
io.print(` in ${projectDir}`);
|
|
258
425
|
io.print('');
|
|
259
426
|
let applied = 0;
|
|
260
427
|
let manual = 0;
|
|
428
|
+
// A ⚠ on a CONNECT step is a guaranteed failure, not a caveat: nothing performs the manual step, so
|
|
429
|
+
// the app never dials the daemon and every tool answers "no browser session connected". `ok` was
|
|
430
|
+
// hardcoded true, so a run that could not possibly work reported success.
|
|
431
|
+
let connectPending = false;
|
|
261
432
|
for (const s of plan.steps) {
|
|
262
433
|
// A side effect that failed to apply is reported as a manual step with its fallback command.
|
|
434
|
+
const note = degraded.get(s.target);
|
|
435
|
+
if (note !== undefined) {
|
|
436
|
+
// Applied, but not the way it was asked for. A NOTICE, not work — the install did happen.
|
|
437
|
+
io.print(` [${STATUS_SYMBOL[StepStatus.NOTICE]}] ${s.title} → ${s.target}`);
|
|
438
|
+
for (const line of note.split('\n'))
|
|
439
|
+
io.print(` ${line}`);
|
|
440
|
+
applied++;
|
|
441
|
+
continue;
|
|
442
|
+
}
|
|
263
443
|
const downgraded = failed.has(s.target) || skipped.has(s.target);
|
|
264
444
|
const status = downgraded ? StepStatus.MANUAL : s.status;
|
|
265
445
|
const detail = skipped.has(s.target)
|
|
@@ -272,8 +452,11 @@ function report(plan, dryRun, failed, skipped, io) {
|
|
|
272
452
|
applied++;
|
|
273
453
|
if (status === StepStatus.MANUAL || status === StepStatus.NOTICE) {
|
|
274
454
|
// A notice prints in full like a manual step — it is worth reading — but is NOT counted as work.
|
|
275
|
-
if (status === StepStatus.MANUAL)
|
|
455
|
+
if (status === StepStatus.MANUAL) {
|
|
276
456
|
manual++;
|
|
457
|
+
if (isConnectStep(s.title))
|
|
458
|
+
connectPending = true;
|
|
459
|
+
}
|
|
277
460
|
for (const line of detail.split('\n'))
|
|
278
461
|
io.print(` ${line}`);
|
|
279
462
|
}
|
|
@@ -282,8 +465,13 @@ function report(plan, dryRun, failed, skipped, io) {
|
|
|
282
465
|
}
|
|
283
466
|
}
|
|
284
467
|
io.print('');
|
|
468
|
+
if (connectPending) {
|
|
469
|
+
io.print('This app will NOT connect until the ⚠ step above is done by hand — Reticle tools will report ' +
|
|
470
|
+
'"no browser session connected" until then. Everything else is already in place.');
|
|
471
|
+
io.print('');
|
|
472
|
+
}
|
|
285
473
|
io.print(restartHint(plan.framework));
|
|
286
|
-
return { ok:
|
|
474
|
+
return { ok: !connectPending, applied, manual };
|
|
287
475
|
}
|
|
288
476
|
/**
|
|
289
477
|
* Perform the apply-step side effects; return the targets whose side effect failed.
|
|
@@ -294,26 +482,87 @@ function report(plan, dryRun, failed, skipped, io) {
|
|
|
294
482
|
* MODULE_NOT_FOUND, so the app stops booting *because* Reticle was installed. A skipped step is a
|
|
295
483
|
* message; a half-wired app is a broken project.
|
|
296
484
|
*/
|
|
485
|
+
/**
|
|
486
|
+
* Are the SDK packages actually on disk, whatever the install step reported?
|
|
487
|
+
*
|
|
488
|
+
* `dependsOnInstall` exists to stop init writing a `next.config.ts` that imports a package which is
|
|
489
|
+
* not there — that took a dev server down once, and installing Reticle must never be why an app
|
|
490
|
+
* stops booting. The invariant it protects is "the import RESOLVES", but it was gated on "our
|
|
491
|
+
* install subprocess exited 0", and those come apart in exactly the situation the failure creates:
|
|
492
|
+
* init tells the user to install by hand, they do, they re-run init, the install step fails again
|
|
493
|
+
* (wrong package manager, not on PATH) and every wiring step is skipped a second time. Reported from
|
|
494
|
+
* a Next 16 app where npm had already installed both packages successfully — leaving an init that
|
|
495
|
+
* could not be retried into working, which is the shape this guard was written to prevent.
|
|
496
|
+
*
|
|
497
|
+
* Reading node_modules answers the real question and costs one `exists` call per package.
|
|
498
|
+
*/
|
|
499
|
+
function sdkPackagesPresent(framework, io) {
|
|
500
|
+
const packages = frameworkPackages(framework);
|
|
501
|
+
return (packages.length > 0 &&
|
|
502
|
+
packages.every((p) => io.exists(`${NODE_MODULES_DIR}/${p}/${PACKAGE_JSON}`)));
|
|
503
|
+
}
|
|
297
504
|
function applyEffects(plan, io) {
|
|
298
505
|
const failed = new Set();
|
|
299
506
|
const skipped = new Set();
|
|
507
|
+
const degraded = new Map();
|
|
300
508
|
let installFailed = false;
|
|
301
509
|
for (const s of plan.steps) {
|
|
302
510
|
if (s.status !== StepStatus.APPLY)
|
|
303
511
|
continue;
|
|
304
|
-
if (installFailed && s.dependsOnInstall
|
|
512
|
+
if (installFailed && true === s.dependsOnInstall) {
|
|
305
513
|
skipped.add(s.target);
|
|
306
514
|
continue;
|
|
307
515
|
}
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
516
|
+
const write = s.write;
|
|
517
|
+
if (write !== undefined) {
|
|
518
|
+
// Confirm the EFFECT, not the intention. Reported from the field (#160): init printed
|
|
519
|
+
// `[✓] Reticle config → .reticle.json` and the file was not there afterward. Nothing checked —
|
|
520
|
+
// no arrangement of the filesystem (a read-only mount, a full disk, an antivirus quarantining
|
|
521
|
+
// a new dotfile) could have turned that tick into anything else. A checkmark that cannot fail
|
|
522
|
+
// is decoration, and this one is the first thing a new user reads. Same shape as #139.
|
|
523
|
+
const wrote = spanSync('init.write', { target: s.target, path: write.path }, () => {
|
|
524
|
+
try {
|
|
525
|
+
io.writeFile(write.path, write.content);
|
|
526
|
+
}
|
|
527
|
+
catch {
|
|
528
|
+
return false; // a throw is the loud version of the same failure
|
|
529
|
+
}
|
|
530
|
+
return io.exists(write.path);
|
|
531
|
+
});
|
|
532
|
+
if (!wrote) {
|
|
533
|
+
failed.add(s.target);
|
|
534
|
+
continue; // do not run this step's exec against a file that is not there
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
// Bound once so the traced call cannot need a `?? ''` fallback — a default there would turn a
|
|
538
|
+
// narrowing mistake into an empty command that silently "succeeds".
|
|
539
|
+
const exec = s.exec;
|
|
540
|
+
// Per-step, because the interesting part of init's wall-clock is WHICH step spent it: a
|
|
541
|
+
// package-manager install and a `claude mcp add` are both subprocesses, and one of them being
|
|
542
|
+
// slow is a completely different problem from the other.
|
|
543
|
+
if (exec !== undefined &&
|
|
544
|
+
!spanSync('init.exec', { target: s.target, command: exec.command }, () => io.exec(exec.command, exec.args))) {
|
|
545
|
+
// A weaker second attempt beats no install at all — but only when it is REPORTED, because the
|
|
546
|
+
// thing it gives up is the version pin that keeps SDK and daemon in step.
|
|
547
|
+
//
|
|
548
|
+
// Traced separately, and it is the reason init can take twice as long as it looks like it
|
|
549
|
+
// should: this is a SECOND full package-manager run, and until it had its own span 1.6 of
|
|
550
|
+
// init's 2.3 seconds simply vanished — the span above accounted for the first attempt and
|
|
551
|
+
// nothing accounted for this one.
|
|
552
|
+
const retry = s.retry;
|
|
553
|
+
if (retry !== undefined &&
|
|
554
|
+
spanSync('init.exec.retry', { target: s.target, command: retry.command }, () => io.exec(retry.command, retry.args))) {
|
|
555
|
+
degraded.set(s.target, retry.note);
|
|
556
|
+
continue;
|
|
557
|
+
}
|
|
311
558
|
failed.add(s.target);
|
|
312
|
-
|
|
559
|
+
// A failed install only blocks the wiring when the packages are genuinely ABSENT. See
|
|
560
|
+
// sdkPackagesPresent: the guard protects "the import resolves", not "our subprocess exited 0".
|
|
561
|
+
if (s.target === DEPS_TARGET && !sdkPackagesPresent(plan.framework, io))
|
|
313
562
|
installFailed = true;
|
|
314
563
|
}
|
|
315
564
|
}
|
|
316
|
-
return { failed, skipped };
|
|
565
|
+
return { failed, skipped, degraded };
|
|
317
566
|
}
|
|
318
567
|
/**
|
|
319
568
|
* Which STEP failed, from the step targets — not from an error string, which would carry paths.
|
|
@@ -338,25 +587,35 @@ const AMBIGUOUS_HEADER = 'Several apps found in this workspace. Re-run `reticle
|
|
|
338
587
|
* Returns null when there is nothing to redirect to — the caller then proceeds here as before.
|
|
339
588
|
*/
|
|
340
589
|
function redirectToWorkspaceApp(options, io, pkgRaw) {
|
|
341
|
-
if (options.redirected
|
|
590
|
+
if (true === options.redirected)
|
|
342
591
|
return null;
|
|
343
592
|
const pkg = JSON.parse(pkgRaw);
|
|
344
593
|
const rootFiles = new Set(io.rootFiles());
|
|
345
594
|
const here = detect({
|
|
346
|
-
pkg: typeof pkg
|
|
595
|
+
pkg: 'object' === typeof pkg && pkg !== null ? pkg : {},
|
|
347
596
|
configFiles: rootFiles,
|
|
348
597
|
lockfiles: new Set(),
|
|
349
598
|
});
|
|
350
599
|
if (here.framework !== Framework.HTML)
|
|
351
600
|
return null; // this directory IS the app
|
|
352
601
|
const apps = findWorkspaceApps(io);
|
|
353
|
-
|
|
602
|
+
// An explicitly named app answers the ambiguity. Refusing to guess is right, but "re-run inside the
|
|
603
|
+
// one you want" is not something a script, a CI step, or an agent that cannot change directory can
|
|
604
|
+
// act on — so the refusal was a dead end for exactly the callers most likely to hit it.
|
|
605
|
+
const chosen = chooseWorkspaceApp(options.app, apps);
|
|
606
|
+
if (!chosen.ok) {
|
|
607
|
+
io.print(chosen.message);
|
|
608
|
+
return { ok: false, applied: 0, manual: 1 };
|
|
609
|
+
}
|
|
610
|
+
const target = chosen.app ?? (1 === apps.length ? apps[0] : undefined);
|
|
354
611
|
if (target === undefined) {
|
|
355
|
-
if (apps.length
|
|
612
|
+
if (0 === apps.length)
|
|
356
613
|
return null; // not a workspace — fall through to the normal HTML plan
|
|
357
614
|
io.print(AMBIGUOUS_HEADER);
|
|
358
615
|
for (const a of apps)
|
|
359
616
|
io.print(` ${a}`);
|
|
617
|
+
io.print('');
|
|
618
|
+
io.print(`Or name one without changing directory: reticle init --app ${apps[0] ?? '<dir>'}`);
|
|
360
619
|
return { ok: false, applied: 0, manual: apps.length };
|
|
361
620
|
}
|
|
362
621
|
io.print(`No app in this directory — wiring ${target} instead.`);
|
|
@@ -364,30 +623,62 @@ function redirectToWorkspaceApp(options, io, pkgRaw) {
|
|
|
364
623
|
return runInit({ ...options, cwd: join(options.cwd, target), redirected: true }, io.scoped(target));
|
|
365
624
|
}
|
|
366
625
|
export function runInit(options, io) {
|
|
626
|
+
const result = runInitSteps(options, io);
|
|
627
|
+
// The ask goes here, not in report(): report() is only the success-shaped path, and the exits that
|
|
628
|
+
// matter most are the ones that never reach it — no package.json, an ambiguous workspace — where
|
|
629
|
+
// setup died before anything ran and the person holding the report has the least to go on. This
|
|
630
|
+
// wrapper is the one point every exit passes through.
|
|
631
|
+
//
|
|
632
|
+
// `redirected` is what keeps it to ONE print: wiring an app in a monorepo re-enters runInit for the
|
|
633
|
+
// chosen directory, and the inner call must not ask again.
|
|
634
|
+
if (true !== options.redirected) {
|
|
635
|
+
io.print('');
|
|
636
|
+
io.print(FEEDBACK_HINT);
|
|
637
|
+
}
|
|
638
|
+
return result;
|
|
639
|
+
}
|
|
640
|
+
function runInitSteps(options, io) {
|
|
367
641
|
const pkgRaw = io.readFile(PACKAGE_JSON);
|
|
368
|
-
|
|
369
|
-
|
|
642
|
+
// Look for the app BEFORE concluding there isn't one.
|
|
643
|
+
//
|
|
644
|
+
// A root with no package.json is not a dead end — it is the ordinary shape of a repo whose app
|
|
645
|
+
// lives one directory down (`frontend/`, `web/`, `client/`) with no manifest at the top. Bailing
|
|
646
|
+
// first made that repo un-instrumentable: `reticle init` said "No package.json found", and
|
|
647
|
+
// `--app frontend`, the flag that exists for exactly this, was never read because the bail came
|
|
648
|
+
// first. Reported twice by the same user, who tried the documented workaround and hit the same
|
|
649
|
+
// wall. Discovery already handles the case (it scans top-level directories, not just declared
|
|
650
|
+
// workspaces); it was simply unreachable.
|
|
651
|
+
//
|
|
652
|
+
// `'{}'` because the redirect only needs the manifest to ask "is THIS directory the app", and a
|
|
653
|
+
// directory with no package.json is definitively not.
|
|
654
|
+
const redirectedEarly = redirectToWorkspaceApp(options, io, pkgRaw ?? '{}');
|
|
655
|
+
if (redirectedEarly !== null)
|
|
656
|
+
return redirectedEarly;
|
|
657
|
+
if (null === pkgRaw) {
|
|
658
|
+
io.print('No package.json found here, and no app directory beneath it either. Run `reticle init` from ' +
|
|
659
|
+
"your app's directory, or from a repo root that contains it.");
|
|
370
660
|
// The onboarding funnel had NO instrumentation, so a setup that died here was indistinguishable
|
|
371
661
|
// from someone who never ran the command — the two failure modes with the most different fixes.
|
|
372
662
|
reportInitOutcome({ ok: false, reason: InitFailure.NO_PACKAGE_JSON });
|
|
373
663
|
return { ok: false, applied: 0, manual: 0 };
|
|
374
664
|
}
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
const plan = buildPlan(gatherPlanInput(options, io, pkgRaw));
|
|
665
|
+
// Init is the flow a user experiences the wait of personally, and the fixture gate measures it at
|
|
666
|
+
// 1–6s per app with no explanation of the spread. These three spans split that number into detect
|
|
667
|
+
// (filesystem probing), plan (pure), and apply (writes + package-manager and CLI subprocesses).
|
|
668
|
+
const plan = spanSync('init.plan', {}, () => buildPlan(gatherPlanInput(options, io, pkgRaw)));
|
|
379
669
|
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);
|
|
670
|
+
? { failed: new Set(), skipped: new Set(), degraded: new Map() }
|
|
671
|
+
: spanSync('init.apply', { steps: plan.steps.length }, () => applyEffects(plan, io));
|
|
672
|
+
const { failed, skipped, degraded } = effects;
|
|
673
|
+
const result = report(plan, options.dryRun, failed, skipped, degraded, io, options.cwd);
|
|
384
674
|
// A dry run is a preview, not an outcome — reporting it would inflate both success and failure.
|
|
385
675
|
if (!options.dryRun) {
|
|
386
676
|
reportInitOutcome({
|
|
387
677
|
ok: result.ok,
|
|
388
678
|
...(result.ok ? {} : { reason: classifyInitFailure(failed) }),
|
|
389
679
|
stack: plan.framework,
|
|
390
|
-
|
|
680
|
+
// The step's REAL final status, not the absence of a failure — see mcp-registered.
|
|
681
|
+
mcpRegistered: wasMcpRegistered(resolvedStatus(plan, MCP_TARGET, failed, skipped)),
|
|
391
682
|
});
|
|
392
683
|
}
|
|
393
684
|
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.
|