@reticlehq/server 2.13.1 → 2.14.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 +8 -18
- package/bin/reticle.js +3 -0
- package/dist/bridge/auth-failure-reason.js +12 -1
- package/dist/bridge/bridge-security.d.ts +8 -0
- package/dist/bridge/bridge-security.js +6 -1
- package/dist/bridge/bridge.d.ts +3 -0
- package/dist/bridge/bridge.js +23 -1
- package/dist/bridge/bridge.test-harness.d.ts +10 -1
- package/dist/bridge/bridge.test-harness.js +7 -1
- package/dist/bridge/pairing-token.d.ts +17 -0
- package/dist/bridge/pairing-token.js +26 -0
- package/dist/bridge/token-auth.d.ts +7 -0
- package/dist/bridge/token-auth.js +7 -1
- package/dist/capsule/capsule-store.d.ts +4 -4
- package/dist/capsule/capsule.d.ts +1 -1
- package/dist/capsule/capsule.js +3 -3
- package/dist/capsule/causal-summary.d.ts +21 -1
- package/dist/capsule/causal-summary.js +8 -6
- package/dist/capsule/divergence.d.ts +1 -1
- package/dist/capsule/divergence.js +26 -10
- package/dist/chromium-launch-options.d.ts +2 -1
- package/dist/cli/adhoc-verdict.d.ts +58 -0
- package/dist/cli/adhoc-verdict.js +115 -0
- package/dist/cli/chromium-hint.d.ts +18 -4
- package/dist/cli/chromium-hint.js +54 -6
- package/dist/cli/cli-doctor.js +1 -2
- package/dist/cli/cli-flow-commands.d.ts +16 -2
- package/dist/cli/cli-flow-commands.js +28 -9
- package/dist/cli/cli-parse-feedback.js +1 -1
- package/dist/cli/cli-parse.d.ts +2 -1
- package/dist/cli/cli-parse.js +38 -0
- package/dist/cli/cli-port.d.ts +5 -13
- package/dist/cli/cli-port.js +5 -38
- package/dist/cli/cli-update-commands.js +4 -4
- package/dist/cli/cli-verify.d.ts +24 -7
- package/dist/cli/cli-verify.js +87 -13
- package/dist/cli/cloud-keystore.d.ts +2 -1
- package/dist/cli/cloud-login.d.ts +2 -1
- package/dist/cli/cloud-session.d.ts +2 -1
- package/dist/cli/cloud-session.js +1 -1
- package/dist/cli/daemon-start-options.d.ts +2 -1
- package/dist/cli/device-flow.d.ts +2 -2
- package/dist/cli/doctor-daemon-line.d.ts +2 -1
- package/dist/cli/doctor-project-line.d.ts +2 -1
- package/dist/cli/doctor-rows.d.ts +2 -1
- package/dist/cli/drive-attach.d.ts +4 -3
- package/dist/cli/gate-hook-message.d.ts +2 -1
- package/dist/cli/status-next-action.js +10 -11
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +21 -9
- package/dist/cloud/account-state.d.ts +20 -0
- package/dist/cloud/account-state.js +65 -0
- package/dist/cloud/cloud-sync.d.ts +46 -3
- package/dist/cloud/cloud-sync.js +116 -0
- package/dist/cloud/sync-cycle.d.ts +5 -4
- package/dist/cloud/sync-cycle.js +1 -1
- package/dist/cloud/sync-daemon.d.ts +3 -19
- package/dist/cloud/sync-daemon.js +2 -2
- package/dist/crawl/crawl-tools.js +1 -1
- package/dist/crawl/crawl.d.ts +5 -5
- package/dist/crawl/crawl.js +138 -4
- package/dist/daemon/daemon-resilience.d.ts +3 -2
- package/dist/daemon/daemon-resilience.js +43 -4
- package/dist/daemon/daemon-resolve.d.ts +0 -9
- package/dist/daemon/daemon-resolve.js +2 -2
- package/dist/daemon/dev-servers.d.ts +1 -1
- package/dist/daemon/dev-servers.js +1 -1
- package/dist/daemon/loopback-alias.d.ts +2 -1
- package/dist/daemon/startup-failure.d.ts +11 -0
- package/dist/daemon/startup-failure.js +46 -0
- package/dist/events/contradictions.d.ts +10 -16
- package/dist/events/contradictions.js +149 -17
- package/dist/events/declared.d.ts +13 -1
- package/dist/events/declared.js +59 -3
- package/dist/events/echo-mismatch.js +75 -6
- package/dist/events/lineage.d.ts +52 -0
- package/dist/events/lineage.js +122 -0
- package/dist/events/net-evidence.d.ts +21 -0
- package/dist/events/net-evidence.js +81 -0
- package/dist/events/predicate-console.d.ts +13 -0
- package/dist/events/predicate-console.js +138 -0
- package/dist/events/predicate-element.d.ts +13 -0
- package/dist/events/predicate-element.js +214 -0
- package/dist/events/predicate-eval.d.ts +15 -3
- package/dist/events/predicate-eval.js +128 -110
- package/dist/events/predicate-parse.js +34 -1
- package/dist/events/predicate-request-body.d.ts +50 -0
- package/dist/events/predicate-request-body.js +120 -0
- package/dist/events/predicate-route.d.ts +21 -1
- package/dist/events/predicate-route.js +149 -21
- package/dist/events/predicate-schema.d.ts +8 -1
- package/dist/events/predicate-schema.js +34 -0
- package/dist/events/predicate.d.ts +2 -0
- package/dist/events/predicate.js +52 -159
- package/dist/events/split-text-miss.d.ts +6 -4
- package/dist/events/split-text-miss.js +12 -5
- package/dist/flows/affected-tools.js +22 -9
- package/dist/flows/annotate.d.ts +2 -1
- package/dist/flows/annotate.js +19 -1
- package/dist/flows/coverage.d.ts +2 -2
- package/dist/flows/flake.d.ts +3 -3
- package/dist/flows/flow-expect-grammar.d.ts +33 -0
- package/dist/flows/flow-expect-grammar.js +155 -0
- package/dist/flows/flow-replay-run.d.ts +1 -1
- package/dist/flows/flow-replay-run.js +28 -15
- package/dist/flows/flow-replay.d.ts +10 -0
- package/dist/flows/flow-replay.js +57 -22
- package/dist/flows/flow-secret-field.d.ts +14 -0
- package/dist/flows/flow-secret-field.js +22 -0
- package/dist/flows/flow-step-runners.d.ts +1 -0
- package/dist/flows/flow-step-runners.js +91 -34
- package/dist/flows/flow-success.js +22 -0
- package/dist/flows/flow-tools.js +27 -8
- package/dist/flows/flow-upload-resolve.d.ts +23 -0
- package/dist/flows/flow-upload-resolve.js +46 -0
- package/dist/flows/flows.d.ts +15 -0
- package/dist/flows/flows.js +17 -14
- package/dist/flows/git-changed.d.ts +23 -4
- package/dist/flows/git-changed.js +32 -6
- package/dist/flows/heal-run.js +2 -2
- package/dist/flows/predicate-to-expect.d.ts +3 -2
- package/dist/flows/predicate-to-expect.js +11 -5
- package/dist/flows/recording-backtrack.d.ts +24 -0
- package/dist/flows/recording-backtrack.js +73 -0
- package/dist/flows/recordings.d.ts +6 -0
- package/dist/flows/replay-halt.d.ts +26 -0
- package/dist/flows/replay-halt.js +30 -0
- package/dist/flows/replay.d.ts +0 -8
- package/dist/flows/replay.js +15 -3
- package/dist/flows/suite-flakes.d.ts +2 -1
- package/dist/flows/verify-change-tools.js +16 -4
- package/dist/honesty/already-true.d.ts +14 -0
- package/dist/honesty/already-true.js +47 -4
- package/dist/honesty/blind-spots.d.ts +3 -1
- package/dist/honesty/blind-spots.js +24 -1
- package/dist/honesty/body-capture-remedy.d.ts +42 -0
- package/dist/honesty/body-capture-remedy.js +95 -0
- package/dist/honesty/feature-capture.d.ts +3 -2
- package/dist/honesty/feature-capture.js +1 -1
- package/dist/honesty/gap-ledger.d.ts +1 -1
- package/dist/honesty/gap-ledger.js +1 -1
- package/dist/honesty/instrumentation-gaps.d.ts +8 -1
- package/dist/honesty/instrumentation-gaps.js +12 -2
- package/dist/honesty/observability.d.ts +4 -3
- package/dist/honesty/tool-hit-rate.d.ts +3 -2
- package/dist/honesty/uncaptured-bodies.d.ts +3 -1
- package/dist/honesty/uncaptured-bodies.js +6 -2
- package/dist/honesty/unsettled.d.ts +9 -0
- package/dist/honesty/unsettled.js +45 -0
- package/dist/honesty/verified.d.ts +32 -1
- package/dist/honesty/verified.js +37 -10
- package/dist/impact/impact-store.d.ts +5 -14
- package/dist/impact/impact-store.js +49 -6
- package/dist/index.js +1 -1
- package/dist/input/network-detail.d.ts +26 -1
- package/dist/input/network-detail.js +44 -12
- package/dist/input/network-mock-tools.js +24 -17
- package/dist/input/real-input.js +1 -1
- package/dist/input/viewport-tools.js +25 -13
- package/dist/intent/inline-intent.d.ts +0 -10
- package/dist/intent/inline-intent.js +1 -1
- package/dist/intent/intent-shard-store.d.ts +3 -2
- package/dist/intent/intent-shard-store.js +1 -1
- package/dist/intent/intent-shard.d.ts +14 -14
- package/dist/intent/intent-shard.js +4 -4
- package/dist/intent/intent-store.d.ts +1 -1
- package/dist/intent/intent-store.js +1 -1
- package/dist/intent/intent-subject.d.ts +2 -1
- package/dist/intent/open-intents.d.ts +3 -2
- package/dist/license/license.d.ts +4 -4
- package/dist/license/license.js +1 -1
- package/dist/mcp/attach-memory.d.ts +2 -1
- package/dist/mcp/client-identity.d.ts +2 -1
- package/dist/mcp/mcp-outage.d.ts +1 -1
- package/dist/mcp/mcp-outage.js +1 -1
- package/dist/mcp/mcp-proxy.d.ts +3 -1
- package/dist/mcp/mcp-proxy.js +32 -7
- package/dist/mcp/mcp.js +3 -1
- package/dist/mcp/proxy-handshake.d.ts +2 -1
- package/dist/mcp/proxy-idle-exit.d.ts +45 -0
- package/dist/mcp/proxy-idle-exit.js +94 -0
- package/dist/mcp/server-instructions.d.ts +2 -1
- package/dist/memory/project-memory.d.ts +2 -3
- package/dist/memory/project-memory.js +1 -1
- package/dist/oracles/propose-consequences.js +5 -5
- package/dist/oracles/self-instrument.d.ts +2 -2
- package/dist/oracles/self-instrument.js +1 -1
- package/dist/platform.d.ts +3 -5
- package/dist/platform.js +3 -5
- package/dist/pool/browser-pool.d.ts +92 -0
- package/dist/pool/browser-pool.js +119 -5
- package/dist/pool/playwright-launcher.js +29 -2
- package/dist/pool/storage-seed.d.ts +23 -0
- package/dist/pool/storage-seed.js +198 -0
- package/dist/project/artifact-root.d.ts +2 -1
- package/dist/project/memory-fs.d.ts +2 -1
- package/dist/project/reticle-dir.js +1 -1
- package/dist/project/session-root.d.ts +8 -0
- package/dist/project/session-root.js +9 -2
- package/dist/runs/reticle-runner.d.ts +14 -1
- package/dist/runs/reticle-runner.js +39 -2
- package/dist/session/attachment-history.d.ts +2 -1
- package/dist/session/fake-session.d.ts +37 -0
- package/dist/session/fake-session.js +81 -0
- package/dist/session/lease-visibility.d.ts +2 -1
- package/dist/session/live-control-tools.js +6 -0
- package/dist/session/no-session-diagnosis.d.ts +17 -1
- package/dist/session/no-session-diagnosis.js +32 -6
- package/dist/session/no-session-next-action.d.ts +14 -0
- package/dist/session/no-session-next-action.js +18 -0
- package/dist/session/no-session-watch.js +46 -3
- package/dist/session/registered-projects.d.ts +54 -0
- package/dist/session/registered-projects.js +73 -0
- package/dist/session/review-store.d.ts +2 -1
- package/dist/session/session-health.d.ts +62 -1
- package/dist/session/session-health.js +82 -2
- package/dist/session/session-manager.d.ts +11 -1
- package/dist/session/session-manager.js +18 -3
- package/dist/session/session-replaced.d.ts +24 -0
- package/dist/session/session-replaced.js +29 -0
- package/dist/session/session-successor.js +20 -4
- package/dist/session/session.d.ts +13 -5
- package/dist/session/session.js +31 -23
- package/dist/setup/agent-configs.d.ts +3 -2
- package/dist/setup/agent-writer.d.ts +3 -2
- package/dist/setup/approval-migration.d.ts +2 -1
- package/dist/setup/approval-migration.js +1 -1
- package/dist/setup/auto-approve.d.ts +11 -19
- package/dist/setup/auto-approve.js +15 -12
- package/dist/{init → setup}/confirm.js +1 -1
- package/dist/setup/desktop-shape.d.ts +3 -2
- package/dist/setup/drive-agent.d.ts +2 -3
- package/dist/setup/drive-agent.js +1 -1
- package/dist/setup/drive-plan.d.ts +3 -2
- package/dist/setup/ensure-daemon.d.ts +2 -1
- package/dist/setup/existing-app.d.ts +12 -0
- package/dist/setup/existing-app.js +13 -0
- package/dist/setup/init-host.d.ts +11 -0
- package/dist/setup/init-host.js +26 -0
- package/dist/setup/init-runtime.d.ts +4 -10
- package/dist/setup/init-runtime.js +23 -16
- package/dist/setup/license-key.d.ts +2 -1
- package/dist/setup/listeners.d.ts +2 -1
- package/dist/setup/loopback-probe-urls.d.ts +18 -0
- package/dist/setup/loopback-probe-urls.js +52 -0
- package/dist/setup/node-effects.d.ts +3 -0
- package/dist/setup/node-effects.js +26 -11
- package/dist/setup/page-probe.d.ts +5 -0
- package/dist/setup/relaunch.d.ts +2 -1
- package/dist/setup/run-setup.d.ts +5 -1
- package/dist/setup/run-setup.js +141 -50
- package/dist/setup/setup-command.d.ts +3 -2
- package/dist/setup/setup-command.js +8 -0
- package/dist/setup/setup-options.d.ts +5 -3
- package/dist/setup/setup-options.js +3 -2
- package/dist/setup/wait-progress.d.ts +28 -0
- package/dist/setup/wait-progress.js +43 -0
- package/dist/telemetry/app-instrumented.d.ts +2 -1
- package/dist/telemetry/app-instrumented.js +1 -1
- package/dist/telemetry/automation-hint.d.ts +1 -1
- package/dist/telemetry/automation-hint.js +1 -1
- package/dist/telemetry/cli-telemetry.js +1 -1
- package/dist/telemetry/connect-failure.d.ts +1 -1
- package/dist/telemetry/connect-failure.js +1 -1
- package/dist/telemetry/crash-cause.d.ts +3 -2
- package/dist/telemetry/crash-cause.js +1 -1
- package/dist/telemetry/daemon-telemetry.d.ts +3 -2
- package/dist/telemetry/daemon-telemetry.js +1 -1
- package/dist/telemetry/error-class.d.ts +3 -2
- package/dist/telemetry/error-class.js +1 -1
- package/dist/telemetry/feedback-cli.js +1 -1
- package/dist/telemetry/feedback-context.d.ts +3 -2
- package/dist/telemetry/feedback-context.js +5 -4
- package/dist/telemetry/feedback-local.d.ts +3 -2
- package/dist/telemetry/feedback.d.ts +4 -3
- package/dist/telemetry/feedback.js +1 -1
- package/dist/telemetry/git-facts.d.ts +2 -2
- package/dist/telemetry/git-facts.js +1 -1
- package/dist/telemetry/identify.d.ts +1 -1
- package/dist/telemetry/identify.js +1 -1
- package/dist/telemetry/init-telemetry.d.ts +2 -14
- package/dist/telemetry/init-telemetry.js +4 -13
- package/dist/telemetry/install-source.d.ts +7 -13
- package/dist/telemetry/install-source.js +7 -29
- package/dist/telemetry/license-activation.d.ts +3 -2
- package/dist/telemetry/license-activation.js +1 -1
- package/dist/telemetry/machine-snapshot.d.ts +1 -1
- package/dist/telemetry/mcp-connection.js +1 -1
- package/dist/telemetry/project-profile.d.ts +0 -14
- package/dist/telemetry/project-profile.js +1 -1
- package/dist/telemetry/proxy-telemetry.js +1 -1
- package/dist/telemetry/session-metrics.d.ts +1 -1
- package/dist/telemetry/session-metrics.js +1 -1
- package/dist/telemetry/telemetry.d.ts +1 -17
- package/dist/telemetry/telemetry.js +4 -4
- package/dist/telemetry/tool-refused.d.ts +1 -1
- package/dist/telemetry/tool-refused.js +1 -1
- package/dist/tools/act-observation.d.ts +3 -2
- package/dist/tools/act-preflight.js +8 -3
- package/dist/tools/act-target.d.ts +4 -0
- package/dist/tools/act-target.js +14 -5
- package/dist/tools/act-tools.d.ts +0 -2
- package/dist/tools/act-tools.js +45 -15
- package/dist/tools/assert-source.d.ts +30 -0
- package/dist/tools/assert-source.js +21 -0
- package/dist/tools/assert-verdict.d.ts +1 -1
- package/dist/tools/assert-verdict.js +14 -4
- package/dist/tools/browser-tools.js +28 -8
- package/dist/tools/complete-snapshot.d.ts +3 -2
- package/dist/tools/dynamic-tools.js +8 -0
- package/dist/tools/error-recovery.d.ts +10 -0
- package/dist/tools/error-recovery.js +23 -0
- package/dist/tools/feedback-tools.js +1 -1
- package/dist/tools/invoke-tool.js +2 -0
- package/dist/tools/lease-tools.d.ts +48 -20
- package/dist/tools/lease-tools.js +311 -76
- package/dist/tools/lineage-tools.d.ts +14 -0
- package/dist/tools/lineage-tools.js +64 -0
- package/dist/tools/merge-tools.js +1 -3
- package/dist/tools/navigate-arrival.d.ts +45 -1
- package/dist/tools/navigate-arrival.js +39 -6
- package/dist/tools/navigate-result.d.ts +8 -1
- package/dist/tools/navigate-result.js +14 -4
- package/dist/tools/numeric-bounds.d.ts +0 -11
- package/dist/tools/numeric-bounds.js +4 -4
- package/dist/tools/observe-tools.js +22 -26
- package/dist/tools/observed-nothing.d.ts +9 -0
- package/dist/tools/observed-nothing.js +2 -1
- package/dist/tools/read-tools.js +12 -17
- package/dist/tools/real-input-attempt.d.ts +14 -6
- package/dist/tools/real-input-attempt.js +75 -18
- package/dist/tools/reconcile-tools.js +2 -1
- package/dist/tools/reload-result.d.ts +6 -1
- package/dist/tools/reload-result.js +7 -2
- package/dist/tools/resolve-target.d.ts +7 -1
- package/dist/tools/resolve-target.js +72 -9
- package/dist/tools/settle-in-flight.d.ts +1 -1
- package/dist/tools/settle-in-flight.js +9 -2
- package/dist/tools/snapshot-delta.d.ts +24 -1
- package/dist/tools/snapshot-delta.js +66 -7
- package/dist/tools/tool-names.d.ts +1 -0
- package/dist/tools/tool-names.js +1 -0
- package/dist/tools/tool-surface.js +20 -2
- package/dist/tools/tools.js +120 -11
- package/dist/tools/verify-next-baton.d.ts +2 -1
- package/dist/update/sdk-sync.d.ts +1 -1
- package/dist/update/sdk-sync.js +1 -1
- package/dist/update/updater.js +1 -1
- package/dist/version/sdk-fix.d.ts +2 -1
- package/dist/version/version-skew.d.ts +0 -8
- package/dist/version/version-skew.js +1 -1
- package/docs/README.md +8 -6
- package/docs/actions.mdx +1 -1
- package/docs/agent-cheatsheet.md +4 -2
- package/docs/architecture.md +1 -1
- package/docs/cli/mcp.mdx +3 -1
- package/docs/cli/watch.mdx +1 -1
- package/docs/cli.mdx +1 -1
- package/docs/desktop.mdx +1 -1
- package/docs/docs.json +8 -3
- package/docs/faq.mdx +9 -0
- package/docs/first-drive.md +136 -0
- package/docs/frameworks.mdx +29 -7
- package/docs/gate-plan.md +2 -2
- package/docs/gates.md +19 -11
- package/docs/getting-started.md +7 -1
- package/docs/http-transport.md +164 -0
- package/docs/index.mdx +5 -3
- package/docs/install-manual.mdx +1 -1
- package/docs/packages/tauri.mdx +1 -1
- package/docs/packages.mdx +15 -3
- package/docs/platform-integration.md +1 -1
- package/docs/predicates.mdx +2 -0
- package/docs/system-map.md +2 -2
- package/docs/telemetry-contract.md +4 -4
- package/docs/telemetry-events.mdx +1 -1
- package/docs/telemetry.md +3 -3
- package/docs/tools-navigate.mdx +14 -6
- package/docs/usage.md +4 -4
- package/package.json +7 -4
- package/dist/init/agent-rules.d.ts +0 -116
- package/dist/init/agent-rules.js +0 -279
- package/dist/init/app-choice.d.ts +0 -20
- package/dist/init/app-choice.js +0 -25
- package/dist/init/astro-host.d.ts +0 -21
- package/dist/init/astro-host.js +0 -27
- package/dist/init/astro-patch.d.ts +0 -33
- package/dist/init/astro-patch.js +0 -300
- package/dist/init/capabilities.d.ts +0 -40
- package/dist/init/capabilities.js +0 -150
- package/dist/init/closing-hint.d.ts +0 -36
- package/dist/init/closing-hint.js +0 -120
- package/dist/init/connect-steps.d.ts +0 -2
- package/dist/init/connect-steps.js +0 -50
- package/dist/init/cra.d.ts +0 -79
- package/dist/init/cra.js +0 -178
- package/dist/init/csp-check.d.ts +0 -38
- package/dist/init/csp-check.js +0 -107
- package/dist/init/csp-doctor.d.ts +0 -32
- package/dist/init/csp-doctor.js +0 -54
- package/dist/init/desktop-doctor.d.ts +0 -41
- package/dist/init/desktop-doctor.js +0 -198
- package/dist/init/detect.d.ts +0 -96
- package/dist/init/detect.js +0 -209
- package/dist/init/dev-script.d.ts +0 -50
- package/dist/init/dev-script.js +0 -66
- package/dist/init/format-generated.d.ts +0 -18
- package/dist/init/format-generated.js +0 -39
- package/dist/init/install-hint.d.ts +0 -9
- package/dist/init/install-hint.js +0 -29
- package/dist/init/mcp-clients.d.ts +0 -89
- package/dist/init/mcp-clients.js +0 -299
- package/dist/init/mcp-registered.d.ts +0 -12
- package/dist/init/mcp-registered.js +0 -14
- package/dist/init/mcp.d.ts +0 -89
- package/dist/init/mcp.js +0 -129
- package/dist/init/next-patch.d.ts +0 -39
- package/dist/init/next-patch.js +0 -196
- package/dist/init/node-io.d.ts +0 -7
- package/dist/init/node-io.js +0 -117
- package/dist/init/non-js-project.d.ts +0 -27
- package/dist/init/non-js-project.js +0 -84
- package/dist/init/patch-kind.d.ts +0 -20
- package/dist/init/patch-kind.js +0 -10
- package/dist/init/plan-framework.d.ts +0 -64
- package/dist/init/plan-framework.js +0 -565
- package/dist/init/plan.d.ts +0 -242
- package/dist/init/plan.js +0 -703
- package/dist/init/preflight.d.ts +0 -28
- package/dist/init/preflight.js +0 -38
- package/dist/init/project-id.d.ts +0 -12
- package/dist/init/project-id.js +0 -25
- package/dist/init/refresh-rules.d.ts +0 -39
- package/dist/init/refresh-rules.js +0 -51
- package/dist/init/run.d.ts +0 -117
- package/dist/init/run.js +0 -806
- package/dist/init/slash-command.d.ts +0 -27
- package/dist/init/slash-command.js +0 -132
- package/dist/init/snippets.d.ts +0 -134
- package/dist/init/snippets.js +0 -572
- package/dist/init/vite-config.d.ts +0 -18
- package/dist/init/vite-config.js +0 -114
- package/dist/init/windows-quote.d.ts +0 -38
- package/dist/init/windows-quote.js +0 -70
- package/dist/init/workspace-apps.d.ts +0 -30
- package/dist/init/workspace-apps.js +0 -178
- package/dist/project/remember-project.d.ts +0 -24
- package/dist/project/remember-project.js +0 -40
- /package/dist/{init → setup}/confirm.d.ts +0 -0
package/dist/init/run.js
DELETED
|
@@ -1,806 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The impure shell for `reticle init`: gather project files via an injected IO surface, build the
|
|
3
|
-
* plan (pure), optionally write the apply-steps, and print a human-readable report. All filesystem
|
|
4
|
-
* access goes through `InitIo` so the orchestration is unit-testable with an in-memory IO.
|
|
5
|
-
*/
|
|
6
|
-
import { dirname, join } from 'node:path';
|
|
7
|
-
import { CSP_FILES } from './csp-doctor.js';
|
|
8
|
-
import { preflightRefusal } from './preflight.js';
|
|
9
|
-
import { noPackageJsonMessage } from './non-js-project.js';
|
|
10
|
-
import { devCommandFrom } from './dev-script.js';
|
|
11
|
-
import { restartHint, FEEDBACK_HINT } from './closing-hint.js';
|
|
12
|
-
import { spanSync } from '../trace.js';
|
|
13
|
-
import { projectIdOf, rememberProjectOnDisk } from '../project/remember-project.js';
|
|
14
|
-
import { detect, Framework, namesAPackageManager, UiLibrary } from './detect.js';
|
|
15
|
-
import { wasMcpRegistered } from './mcp-registered.js';
|
|
16
|
-
import { pickAstroHost } from './astro-host.js';
|
|
17
|
-
import { findWorkspaceApps, NEXT_CONFIG_CANDIDATES, PACKAGE_JSON, VITE_CONFIG_CANDIDATES, } from './workspace-apps.js';
|
|
18
|
-
import { chooseWorkspaceApp } from './app-choice.js';
|
|
19
|
-
import { isConnectStep } from './connect-steps.js';
|
|
20
|
-
import { CURSOR_RULE_PATH, RETICLE_MD_PATH } from './agent-rules.js';
|
|
21
|
-
import { CRA_ENV_PATH } from './cra.js';
|
|
22
|
-
import { defaultPairingTokenDir, readOrCreatePairingTokenSync } from '../bridge/pairing-token.js';
|
|
23
|
-
import { formatGeneratedSource } from './format-generated.js';
|
|
24
|
-
/** CRA's bundled entry, in the order create-react-app itself generates them. */
|
|
25
|
-
const CRA_ENTRY_CANDIDATES = ['src/index.tsx', 'src/index.jsx', 'src/index.ts', 'src/index.js'];
|
|
26
|
-
function craEntryOf(io) {
|
|
27
|
-
for (const path of CRA_ENTRY_CANDIDATES) {
|
|
28
|
-
const source = io.readFile(path);
|
|
29
|
-
if (source !== null)
|
|
30
|
-
return { path, source };
|
|
31
|
-
}
|
|
32
|
-
return null;
|
|
33
|
-
}
|
|
34
|
-
/**
|
|
35
|
-
* The daemon's pairing token, minted here if nothing has written it yet.
|
|
36
|
-
*
|
|
37
|
-
* `init` used to READ the file and return empty when the daemon had never started. The CDN snippet
|
|
38
|
-
* inlined that empty value permanently, and regenerating the token made the pasted literal stale.
|
|
39
|
-
* Same mint as the daemon (`readOrCreatePairingToken`), honours `RETICLE_PAIRING_TOKEN_DIR`.
|
|
40
|
-
*/
|
|
41
|
-
function readPairingToken() {
|
|
42
|
-
return readOrCreatePairingTokenSync(defaultPairingTokenDir()) ?? '';
|
|
43
|
-
}
|
|
44
|
-
import { DEPS_TARGET, RETICLE_CONFIG_FILE, frameworkPackages, MCP_TARGET, buildPlan, StepStatus, } from './plan.js';
|
|
45
|
-
import { claudeAvailableProbe, claudeExistsProbe } from './mcp.js';
|
|
46
|
-
import { reticleDevLocation } from './next-patch.js';
|
|
47
|
-
import { scanTestids, storeHints, scanStores } from './capabilities.js';
|
|
48
|
-
import { fileBackedClients, clientMarkerRelPath, ConfigScope, McpClient, CURSOR_PROJECT_MARKER, } from './mcp-clients.js';
|
|
49
|
-
import { deriveProjectId, packageName } from './project-id.js';
|
|
50
|
-
import { VITE_DEV_MODULE_PATH, connectArgWithToken, staticPageSnippet } from './snippets.js';
|
|
51
|
-
import { CLAUDE_COMMAND_PATH, CURSOR_COMMAND_PATH } from './slash-command.js';
|
|
52
|
-
import { SERVER_VERSION } from '../version/server-version.js';
|
|
53
|
-
import { InitFailure, reportInitOutcome } from '../telemetry/init-telemetry.js';
|
|
54
|
-
/** Lockfile basenames, in package-manager preference order (mirrors detect.ts). */
|
|
55
|
-
const LOCKFILE_NAMES = [
|
|
56
|
-
'pnpm-lock.yaml',
|
|
57
|
-
'yarn.lock',
|
|
58
|
-
'bun.lockb',
|
|
59
|
-
'bun.lock',
|
|
60
|
-
'package-lock.json',
|
|
61
|
-
];
|
|
62
|
-
/**
|
|
63
|
-
* Resolve the lockfiles set used to pick the package manager. A lockfile in the project root wins;
|
|
64
|
-
* otherwise we walk UP the directory tree (monorepos keep the lockfile at the workspace root, not in
|
|
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.
|
|
73
|
-
*/
|
|
74
|
-
export function resolveLockfiles(rootFiles, cwd, io, nodeModulesMarkers = new Set()) {
|
|
75
|
-
const set = new Set(rootFiles);
|
|
76
|
-
if (LOCKFILE_NAMES.some((name) => set.has(name)))
|
|
77
|
-
return set; // local lockfile is authoritative
|
|
78
|
-
if (namesAPackageManager(nodeModulesMarkers))
|
|
79
|
-
return set;
|
|
80
|
-
let dir = cwd;
|
|
81
|
-
for (let depth = 0; depth < 50; depth++) {
|
|
82
|
-
for (const name of LOCKFILE_NAMES) {
|
|
83
|
-
if (io.exists(join(dir, name))) {
|
|
84
|
-
set.add(name);
|
|
85
|
-
return set;
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
const parent = dirname(dir);
|
|
89
|
-
if (parent === dir)
|
|
90
|
-
break; // filesystem root
|
|
91
|
-
dir = parent;
|
|
92
|
-
}
|
|
93
|
-
return set;
|
|
94
|
-
}
|
|
95
|
-
const NODE_MODULES_DIR = 'node_modules';
|
|
96
|
-
/**
|
|
97
|
-
* Root-layout candidates, App Router only. `--src-dir` apps keep theirs under `src/app`, and the
|
|
98
|
-
* ReticleDev component has to land NEXT TO the layout or the relative import it generates is dead.
|
|
99
|
-
*/
|
|
100
|
-
const NEXT_LAYOUT_CANDIDATES = [
|
|
101
|
-
'app/layout.tsx',
|
|
102
|
-
'app/layout.jsx',
|
|
103
|
-
'app/layout.js',
|
|
104
|
-
'src/app/layout.tsx',
|
|
105
|
-
'src/app/layout.jsx',
|
|
106
|
-
'src/app/layout.js',
|
|
107
|
-
];
|
|
108
|
-
/**
|
|
109
|
-
* Pages Router mount points, checked only when there is no App Router layout. A Pages app has no
|
|
110
|
-
* `app/` directory at all, so writing the component there produced a file nothing imported.
|
|
111
|
-
*/
|
|
112
|
-
const NEXT_PAGES_APP_CANDIDATES = [
|
|
113
|
-
'pages/_app.tsx',
|
|
114
|
-
'pages/_app.jsx',
|
|
115
|
-
'pages/_app.js',
|
|
116
|
-
'src/pages/_app.tsx',
|
|
117
|
-
'src/pages/_app.jsx',
|
|
118
|
-
'src/pages/_app.js',
|
|
119
|
-
];
|
|
120
|
-
const SVELTEKIT_HOOKS = 'src/hooks.client.ts';
|
|
121
|
-
const SOURCE_FILE = /\.(tsx|jsx|ts|js|svelte|vue|astro)$/;
|
|
122
|
-
/** Files read for the testid scan. A capabilities block is a hint; reading a whole repo for it is not. */
|
|
123
|
-
const MAX_SCANNED_FILES = 200;
|
|
124
|
-
/** Directories that never hold the app's own source, and are expensive or misleading to read. */
|
|
125
|
-
const NOT_SOURCE_DIRS = new Set([
|
|
126
|
-
'node_modules',
|
|
127
|
-
'dist',
|
|
128
|
-
'build',
|
|
129
|
-
'out',
|
|
130
|
-
'coverage',
|
|
131
|
-
'target',
|
|
132
|
-
'public',
|
|
133
|
-
'static',
|
|
134
|
-
'.next',
|
|
135
|
-
'.svelte-kit',
|
|
136
|
-
'.nuxt',
|
|
137
|
-
]);
|
|
138
|
-
/** How far below the app root to look. Deep enough for `modules/users/UserList.tsx`, not a repo crawl. */
|
|
139
|
-
const MAX_SCAN_DEPTH = 5;
|
|
140
|
-
/**
|
|
141
|
-
* Read a bounded set of the app's source files, for the `data-testid` scan.
|
|
142
|
-
*
|
|
143
|
-
* This used to walk the fixed list `src, src/components, src/pages, app, components`. Reported from
|
|
144
|
-
* the field (#318) by a repo whose app is at `src/admin`: the scan read directories that were not
|
|
145
|
-
* the app's and correctly reported finding nothing in them, so `init` said "no data-testid values
|
|
146
|
-
* yet" about an app with several — which makes an agent go and write the ones already there. That
|
|
147
|
-
* list had already grown once for a `frontend/` app, and a third report of the same shape is what
|
|
148
|
-
* says the answer is not another name in a list. So: walk the app root, bounded by depth and by the
|
|
149
|
-
* same file cap as before.
|
|
150
|
-
*/
|
|
151
|
-
function readSourceFiles(io) {
|
|
152
|
-
const out = [];
|
|
153
|
-
const read = (path) => {
|
|
154
|
-
const content = io.readFile(path);
|
|
155
|
-
if (content !== null)
|
|
156
|
-
out.push({ path, source: content });
|
|
157
|
-
};
|
|
158
|
-
for (const name of io.rootFiles()) {
|
|
159
|
-
if (out.length >= MAX_SCANNED_FILES)
|
|
160
|
-
return out;
|
|
161
|
-
if (SOURCE_FILE.test(name))
|
|
162
|
-
read(name);
|
|
163
|
-
}
|
|
164
|
-
const walk = (dir, depth) => {
|
|
165
|
-
for (const name of io.listFiles(dir)) {
|
|
166
|
-
if (out.length >= MAX_SCANNED_FILES)
|
|
167
|
-
return;
|
|
168
|
-
if (SOURCE_FILE.test(name))
|
|
169
|
-
read(`${dir}/${name}`);
|
|
170
|
-
}
|
|
171
|
-
if (depth >= MAX_SCAN_DEPTH)
|
|
172
|
-
return;
|
|
173
|
-
for (const sub of io.listDirs(dir)) {
|
|
174
|
-
if (out.length >= MAX_SCANNED_FILES)
|
|
175
|
-
return;
|
|
176
|
-
if (sub.startsWith('.') || NOT_SOURCE_DIRS.has(sub))
|
|
177
|
-
continue;
|
|
178
|
-
walk(`${dir}/${sub}`, depth + 1);
|
|
179
|
-
}
|
|
180
|
-
};
|
|
181
|
-
for (const dir of io.listDirs('.')) {
|
|
182
|
-
if (out.length >= MAX_SCANNED_FILES)
|
|
183
|
-
return out;
|
|
184
|
-
if (dir.startsWith('.') || NOT_SOURCE_DIRS.has(dir))
|
|
185
|
-
continue;
|
|
186
|
-
walk(dir, 1);
|
|
187
|
-
}
|
|
188
|
-
return out;
|
|
189
|
-
}
|
|
190
|
-
/** Direct dependency names, for naming the state libraries an app actually has. */
|
|
191
|
-
function dependencyNames(pkg) {
|
|
192
|
-
const p = (pkg ?? {});
|
|
193
|
-
return new Set([
|
|
194
|
-
...Object.keys(p['dependencies'] ?? {}),
|
|
195
|
-
...Object.keys(p['devDependencies'] ?? {}),
|
|
196
|
-
]);
|
|
197
|
-
}
|
|
198
|
-
const ASTRO_CONFIG_CANDIDATES = [
|
|
199
|
-
'astro.config.mjs',
|
|
200
|
-
'astro.config.js',
|
|
201
|
-
'astro.config.ts',
|
|
202
|
-
'astro.config.cjs',
|
|
203
|
-
];
|
|
204
|
-
/** Where a conventional Astro app keeps the layout every page wraps itself in. */
|
|
205
|
-
const ASTRO_LAYOUTS_DIR = 'src/layouts';
|
|
206
|
-
/** Also searched: an app with no layouts directory renders the document straight from a page. */
|
|
207
|
-
const ASTRO_PAGES_DIR = 'src/pages';
|
|
208
|
-
/**
|
|
209
|
-
* `⚠` means WORK LEFT TO DO and nothing else — it is what an agent (and the release gate) counts to
|
|
210
|
-
* decide whether the install finished. A notice gets its own mark so "steps remaining" can reach zero
|
|
211
|
-
* on a working install that happens to be on an ungated stack.
|
|
212
|
-
*/
|
|
213
|
-
/**
|
|
214
|
-
* A step's status AFTER the run, which is what actually happened to it.
|
|
215
|
-
*
|
|
216
|
-
* `report` applies the same downgrade when printing: a step that failed or was skipped is shown as
|
|
217
|
-
* MANUAL whatever it planned to be. Reading the planned status alone would say a step applied when
|
|
218
|
-
* the run had already given up on it.
|
|
219
|
-
*/
|
|
220
|
-
function resolvedStatus(plan, target, failed, skipped) {
|
|
221
|
-
const step = plan.steps.find((s) => s.target === target);
|
|
222
|
-
if (step === undefined)
|
|
223
|
-
return undefined;
|
|
224
|
-
if (failed.has(target) || skipped.has(target))
|
|
225
|
-
return StepStatus.MANUAL;
|
|
226
|
-
return step.status;
|
|
227
|
-
}
|
|
228
|
-
const STATUS_SYMBOL = {
|
|
229
|
-
[StepStatus.APPLY]: '✓',
|
|
230
|
-
[StepStatus.MANUAL]: '⚠',
|
|
231
|
-
[StepStatus.ALREADY]: '·',
|
|
232
|
-
[StepStatus.SKIP]: '–',
|
|
233
|
-
[StepStatus.NOTICE]: 'ℹ',
|
|
234
|
-
};
|
|
235
|
-
function firstPresent(files, candidates) {
|
|
236
|
-
for (const c of candidates)
|
|
237
|
-
if (files.has(c))
|
|
238
|
-
return c;
|
|
239
|
-
return null;
|
|
240
|
-
}
|
|
241
|
-
/**
|
|
242
|
-
* The directory the human's agent runs in, when it is not the app's directory.
|
|
243
|
-
*
|
|
244
|
-
* `undefined` for a single-package repo, which keeps every agent-file path project-relative exactly
|
|
245
|
-
* as it was — the two roots are the same there, which is why writing them beside the app went
|
|
246
|
-
* unnoticed until a repo with its app at `src/admin` reported it.
|
|
247
|
-
*/
|
|
248
|
-
function agentRootOf(options) {
|
|
249
|
-
const root = options.agentRoot;
|
|
250
|
-
return root === undefined || root === options.cwd ? undefined : root;
|
|
251
|
-
}
|
|
252
|
-
function gatherPlanInput(options, io, pkg) {
|
|
253
|
-
// Stable identity derived from the app's package.json name + root, so it survives port changes.
|
|
254
|
-
const projectId = deriveProjectId(packageName(pkg), options.cwd);
|
|
255
|
-
const rootFiles = new Set(io.rootFiles());
|
|
256
|
-
const nodeModulesMarkers = new Set(io.listFiles('node_modules'));
|
|
257
|
-
const detectInput = {
|
|
258
|
-
pkg: 'object' === typeof pkg && pkg !== null ? pkg : {},
|
|
259
|
-
configFiles: rootFiles,
|
|
260
|
-
// Walk up for the lockfile so a monorepo sub-package picks the workspace's package manager —
|
|
261
|
-
// unless this package's own installed tree already answers it, which outranks an inherited one.
|
|
262
|
-
lockfiles: resolveLockfiles(rootFiles, options.cwd, io, nodeModulesMarkers),
|
|
263
|
-
// An already-installed tree names its own manager, which matters when no lockfile is committed.
|
|
264
|
-
nodeModulesMarkers,
|
|
265
|
-
};
|
|
266
|
-
const detection = detect(detectInput);
|
|
267
|
-
const vitePath = firstPresent(rootFiles, VITE_CONFIG_CANDIDATES);
|
|
268
|
-
const viteSource = null === vitePath ? null : io.readFile(vitePath);
|
|
269
|
-
const viteConfig = vitePath !== null && viteSource !== null ? { path: vitePath, source: viteSource } : null;
|
|
270
|
-
// Global MCP registration targets each agent that's present: Claude via its CLI, Cursor via its
|
|
271
|
-
// global config file. Only probe when the MCP step is in play.
|
|
272
|
-
const availableProbe = claudeAvailableProbe();
|
|
273
|
-
const claudeCli = options.mcp ? io.probe(availableProbe.command, availableProbe.args) : false;
|
|
274
|
-
const existsProbe = claudeExistsProbe();
|
|
275
|
-
const mcpExists = claudeCli ? io.probe(existsProbe.command, existsProbe.args) : false;
|
|
276
|
-
// Every MCP client this machine shows evidence of. Conservative and one-directional: we write
|
|
277
|
-
// into a config a client ALREADY has, and never create ~/.gemini or ~/.codeium for somebody who
|
|
278
|
-
// does not use them.
|
|
279
|
-
const detectedClients = options.mcp
|
|
280
|
-
? fileBackedClients()
|
|
281
|
-
.map((spec) => {
|
|
282
|
-
const marker = clientMarkerRelPath(spec);
|
|
283
|
-
const absolute = spec.scope === ConfigScope.HOME ? join(io.homeDir(), spec.relPath) : spec.relPath;
|
|
284
|
-
const markerPath = spec.scope === ConfigScope.HOME ? join(io.homeDir(), marker) : marker;
|
|
285
|
-
// A fresh Cursor profile has not written ~/.cursor yet; the project-level .cursor/ is the
|
|
286
|
-
// fallback that kept that real case working. Same signal, project scope.
|
|
287
|
-
const projectFallback = spec.id === McpClient.CURSOR && !io.exists(markerPath)
|
|
288
|
-
? io.exists(CURSOR_PROJECT_MARKER)
|
|
289
|
-
: false;
|
|
290
|
-
if (!io.exists(markerPath) && !projectFallback)
|
|
291
|
-
return null;
|
|
292
|
-
return {
|
|
293
|
-
id: spec.id,
|
|
294
|
-
configPath: absolute,
|
|
295
|
-
existing: io.readFile(absolute),
|
|
296
|
-
};
|
|
297
|
-
})
|
|
298
|
-
.filter((entry) => entry !== null)
|
|
299
|
-
: [];
|
|
300
|
-
const astroPath = firstPresent(rootFiles, ASTRO_CONFIG_CANDIDATES);
|
|
301
|
-
const astroSource = null === astroPath ? null : io.readFile(astroPath);
|
|
302
|
-
// Which file owns the DOCUMENT, not how many files sit in a directory. The old rule ("exactly one
|
|
303
|
-
// .astro in src/layouts") fired on neither real Astro app: one has no layouts directory and
|
|
304
|
-
// renders from src/pages/index.astro, the other has three files there of which two are partials.
|
|
305
|
-
// `</body>` is the discriminator — see astro-host.
|
|
306
|
-
const astroCandidates = [ASTRO_LAYOUTS_DIR, ASTRO_PAGES_DIR].flatMap((dir) => io
|
|
307
|
-
.listFiles(dir)
|
|
308
|
-
.filter((f) => f.endsWith('.astro'))
|
|
309
|
-
.map((f) => ({ path: `${dir}/${f}`, source: io.readFile(`${dir}/${f}`) }))
|
|
310
|
-
.filter((c) => c.source !== null));
|
|
311
|
-
const astroHost = pickAstroHost(astroCandidates);
|
|
312
|
-
const layoutRelPath = astroHost?.path ?? null;
|
|
313
|
-
const astroLayoutSource = astroHost?.source ?? null;
|
|
314
|
-
const nextConfigFile = firstPresent(rootFiles, NEXT_CONFIG_CANDIDATES);
|
|
315
|
-
// App Router first; a Pages Router app has no layout, and its mount point is pages/_app.
|
|
316
|
-
const layoutPath = NEXT_LAYOUT_CANDIDATES.find((p) => io.exists(p)) ??
|
|
317
|
-
NEXT_PAGES_APP_CANDIDATES.find((p) => io.exists(p)) ??
|
|
318
|
-
null;
|
|
319
|
-
const layoutSource = null === layoutPath ? null : io.readFile(layoutPath);
|
|
320
|
-
// Where the component goes depends on WHICH router mounts it: `pages/` routes on presence, so a
|
|
321
|
-
// component there becomes a broken route; `app/` routes on filename, so a sibling is inert.
|
|
322
|
-
const devLocation = reticleDevLocation(layoutPath ?? 'app/layout.tsx', detection.typescript);
|
|
323
|
-
// Read once: both the testid scan and the store scan want the same bounded set of files.
|
|
324
|
-
const sourceFiles = readSourceFiles(io);
|
|
325
|
-
const agentRoot = agentRootOf(options);
|
|
326
|
-
const agentFile = (relPath) => agentRoot === undefined ? relPath : join(agentRoot, relPath);
|
|
327
|
-
const cspSources = {};
|
|
328
|
-
for (const file of CSP_FILES) {
|
|
329
|
-
const source = io.readFile(file);
|
|
330
|
-
if (null !== source)
|
|
331
|
-
cspSources[file] = source;
|
|
332
|
-
}
|
|
333
|
-
return {
|
|
334
|
-
detection,
|
|
335
|
-
captureBodies: options.captureBodies,
|
|
336
|
-
cspSources,
|
|
337
|
-
claudeCli,
|
|
338
|
-
mcpExists,
|
|
339
|
-
platform: process.platform,
|
|
340
|
-
detectedClients,
|
|
341
|
-
cursorProjectPresent: io.exists(CURSOR_PROJECT_MARKER),
|
|
342
|
-
viteConfig,
|
|
343
|
-
astroConfig: astroPath !== null && astroSource !== null ? { path: astroPath, source: astroSource } : null,
|
|
344
|
-
astroLayout: layoutRelPath !== null && astroLayoutSource !== null
|
|
345
|
-
? { path: layoutRelPath, source: astroLayoutSource }
|
|
346
|
-
: null,
|
|
347
|
-
astroEnvDts: io.readFile('src/env.d.ts'),
|
|
348
|
-
nextConfigFile,
|
|
349
|
-
nextConfigSource: null === nextConfigFile ? null : io.readFile(nextConfigFile),
|
|
350
|
-
nextLayout: layoutPath !== null && layoutSource !== null
|
|
351
|
-
? { path: layoutPath, source: layoutSource }
|
|
352
|
-
: null,
|
|
353
|
-
// Capabilities: scanned, never asked for. Bounded — a hint for the agent, not a repo index.
|
|
354
|
-
testids: scanTestids(sourceFiles.map((f) => f.source)),
|
|
355
|
-
storeHints: storeHints(dependencyNames(pkg)),
|
|
356
|
-
foundStores: scanStores(sourceFiles, dependencyNames(pkg)),
|
|
357
|
-
nextFoundStores: scanStores(sourceFiles, dependencyNames(pkg), dirname(devLocation.path)),
|
|
358
|
-
viteDevModuleExists: io.exists(VITE_DEV_MODULE_PATH),
|
|
359
|
-
nextReticleDevPath: devLocation.path,
|
|
360
|
-
nextReticleDevImport: devLocation.importSpecifier,
|
|
361
|
-
nextReticleDevExists: io.exists(devLocation.path),
|
|
362
|
-
nextReticleDevSource: io.readFile(devLocation.path),
|
|
363
|
-
svelteKitHooksExists: io.exists(SVELTEKIT_HOOKS),
|
|
364
|
-
craEntry: craEntryOf(io),
|
|
365
|
-
craEnv: io.readFile(CRA_ENV_PATH),
|
|
366
|
-
pairingToken: readPairingToken(),
|
|
367
|
-
reticleConfigExists: io.exists(RETICLE_CONFIG_FILE),
|
|
368
|
-
// The CONTENT, so a config that exists can be checked rather than trusted — a `"port"` set to
|
|
369
|
-
// the app's own dev-server port used to survive every re-run of `init`.
|
|
370
|
-
reticleConfigSource: io.readFile(RETICLE_CONFIG_FILE),
|
|
371
|
-
// Read the agent instruction files so the rule merge stays idempotent across re-runs — from the
|
|
372
|
-
// agent's own root, or the merge would idempotently check a file it is not going to write.
|
|
373
|
-
claudeMdContent: io.readFile(agentFile('CLAUDE.md')),
|
|
374
|
-
agentsMdContent: io.readFile(agentFile('AGENTS.md')),
|
|
375
|
-
reticleMdContent: io.readFile(agentFile(RETICLE_MD_PATH)),
|
|
376
|
-
cursorRuleContent: io.readFile(agentFile(CURSOR_RULE_PATH)),
|
|
377
|
-
claudeCommandContent: io.readFile(agentFile(CLAUDE_COMMAND_PATH)),
|
|
378
|
-
cursorCommandContent: io.readFile(agentFile(CURSOR_COMMAND_PATH)),
|
|
379
|
-
...(agentRoot === undefined
|
|
380
|
-
? {}
|
|
381
|
-
: {
|
|
382
|
-
agentFileRoot: agentRoot,
|
|
383
|
-
// The config the AGENT's cwd already has, if any — see agentRootConfigStep.
|
|
384
|
-
agentRootConfigSource: io.readFile(agentFile(RETICLE_CONFIG_FILE)),
|
|
385
|
-
}),
|
|
386
|
-
options: {
|
|
387
|
-
port: options.port,
|
|
388
|
-
mcp: options.mcp,
|
|
389
|
-
install: options.install,
|
|
390
|
-
projectId,
|
|
391
|
-
// The SDK must match the CLI asking for it — see pinnedPackages.
|
|
392
|
-
sdkVersion: SERVER_VERSION,
|
|
393
|
-
},
|
|
394
|
-
};
|
|
395
|
-
}
|
|
396
|
-
/** Where workspace tooling conventionally puts packages. */
|
|
397
|
-
/** pnpm's workspace declaration, read when present — it is authoritative about where packages live. */
|
|
398
|
-
const SKIPPED_DETAIL = 'skipped — the dependency install above failed, and wiring the app to a package that is not ' +
|
|
399
|
-
'installed stops it booting. Run that install, then re-run `reticle init`.';
|
|
400
|
-
function report(plan, dryRun, failed, skipped, degraded, io, projectDir, agentRoot,
|
|
401
|
-
/** The project's own dev command, so the closing line names what a human would type. */
|
|
402
|
-
devCommand,
|
|
403
|
-
/**
|
|
404
|
-
* Whether this run continues into the phases that boot the app and drive it.
|
|
405
|
-
*
|
|
406
|
-
* The closing hint tells the reader to restart their dev server and then drive a flow. When those
|
|
407
|
-
* are the very next things this command does, printing them is worse than noise: it is an
|
|
408
|
-
* instruction to do by hand what is about to happen automatically, three lines before it happens.
|
|
409
|
-
*/
|
|
410
|
-
continuesToRuntime = false) {
|
|
411
|
-
io.print(dryRun ? 'reticle init (dry run, no files written)' : 'reticle init');
|
|
412
|
-
// Every path below is printed RELATIVE, and until now nothing said what to. Reported from the
|
|
413
|
-
// field as "[✓] Reticle config → .reticle.json" followed by the file not being there: the app was
|
|
414
|
-
// in `frontend/`, init redirected into it, and the report's `.reticle.json` was true about a
|
|
415
|
-
// directory the reader was not standing in. One line makes every path in the report unambiguous.
|
|
416
|
-
io.print(` in ${projectDir}`);
|
|
417
|
-
// The agent files go where the AGENT is, which after a redirect is not where the app is. Said out
|
|
418
|
-
// loud because it changes where `/reticle` will exist, and a reader who assumes one directory for
|
|
419
|
-
// everything goes looking in the wrong one (#318).
|
|
420
|
-
if (agentRoot !== undefined)
|
|
421
|
-
io.print(` agent files in ${agentRoot} (where /reticle will exist)`);
|
|
422
|
-
io.print('');
|
|
423
|
-
let applied = 0;
|
|
424
|
-
let manual = 0;
|
|
425
|
-
// A ⚠ on a CONNECT step is a guaranteed failure, not a caveat: nothing performs the manual step, so
|
|
426
|
-
// the app never dials the daemon and every tool answers "no browser session connected". `ok` was
|
|
427
|
-
// hardcoded true, so a run that could not possibly work reported success.
|
|
428
|
-
let connectPending = false;
|
|
429
|
-
for (const s of plan.steps) {
|
|
430
|
-
// A side effect that failed to apply is reported as a manual step with its fallback command.
|
|
431
|
-
const note = degraded.get(s.target);
|
|
432
|
-
if (note !== undefined) {
|
|
433
|
-
// Applied, but not the way it was asked for. A NOTICE, not work — the install did happen.
|
|
434
|
-
io.print(` [${STATUS_SYMBOL[StepStatus.NOTICE]}] ${s.title} → ${s.target}`);
|
|
435
|
-
for (const line of note.split('\n'))
|
|
436
|
-
io.print(` ${line}`);
|
|
437
|
-
applied++;
|
|
438
|
-
continue;
|
|
439
|
-
}
|
|
440
|
-
const downgraded = failed.has(s.target) || skipped.has(s.target);
|
|
441
|
-
const status = downgraded ? StepStatus.MANUAL : s.status;
|
|
442
|
-
const detail = skipped.has(s.target)
|
|
443
|
-
? SKIPPED_DETAIL
|
|
444
|
-
: downgraded && s.exec !== undefined
|
|
445
|
-
? `step failed — run manually: ${s.exec.fallback}`
|
|
446
|
-
: s.detail;
|
|
447
|
-
io.print(` [${STATUS_SYMBOL[status]}] ${s.title} → ${s.target}`);
|
|
448
|
-
if (status === StepStatus.APPLY)
|
|
449
|
-
applied++;
|
|
450
|
-
if (status === StepStatus.MANUAL || status === StepStatus.NOTICE) {
|
|
451
|
-
// A notice prints in full like a manual step — it is worth reading — but is NOT counted as work.
|
|
452
|
-
if (status === StepStatus.MANUAL) {
|
|
453
|
-
manual++;
|
|
454
|
-
if (isConnectStep(s.title))
|
|
455
|
-
connectPending = true;
|
|
456
|
-
}
|
|
457
|
-
for (const line of detail.split('\n'))
|
|
458
|
-
io.print(` ${line}`);
|
|
459
|
-
}
|
|
460
|
-
else if (detail.length > 0) {
|
|
461
|
-
io.print(` ${detail}`);
|
|
462
|
-
}
|
|
463
|
-
}
|
|
464
|
-
io.print('');
|
|
465
|
-
if (connectPending) {
|
|
466
|
-
io.print('This app will NOT connect until the ⚠ step above is done by hand — Reticle tools will report ' +
|
|
467
|
-
'"no browser session connected" until then. Everything else is already in place.');
|
|
468
|
-
io.print('');
|
|
469
|
-
}
|
|
470
|
-
if (!continuesToRuntime) {
|
|
471
|
-
io.print(restartHint(plan.framework, resolvedStatus(plan, MCP_TARGET, failed, skipped), devCommand));
|
|
472
|
-
}
|
|
473
|
-
return { ok: !connectPending, applied, manual };
|
|
474
|
-
}
|
|
475
|
-
/**
|
|
476
|
-
* Perform the apply-step side effects; return the targets whose side effect failed.
|
|
477
|
-
*
|
|
478
|
-
* Steps run in plan order, which puts the dependency install BEFORE everything that imports what it
|
|
479
|
-
* installs. If it fails, the wiring is skipped rather than applied: patching `next.config.ts` to
|
|
480
|
-
* import a `@reticlehq/next` that was never installed takes the dev server down with
|
|
481
|
-
* MODULE_NOT_FOUND, so the app stops booting *because* Reticle was installed. A skipped step is a
|
|
482
|
-
* message; a half-wired app is a broken project.
|
|
483
|
-
*/
|
|
484
|
-
/**
|
|
485
|
-
* Are the SDK packages actually on disk, whatever the install step reported?
|
|
486
|
-
*
|
|
487
|
-
* `dependsOnInstall` exists to stop init writing a `next.config.ts` that imports a package which is
|
|
488
|
-
* not there — that took a dev server down once, and installing Reticle must never be why an app
|
|
489
|
-
* stops booting. The invariant it protects is "the import RESOLVES", but it was gated on "our
|
|
490
|
-
* install subprocess exited 0", and those come apart in exactly the situation the failure creates:
|
|
491
|
-
* init tells the user to install by hand, they do, they re-run init, the install step fails again
|
|
492
|
-
* (wrong package manager, not on PATH) and every wiring step is skipped a second time. Reported from
|
|
493
|
-
* a Next 16 app where npm had already installed both packages successfully — leaving an init that
|
|
494
|
-
* could not be retried into working, which is the shape this guard was written to prevent.
|
|
495
|
-
*
|
|
496
|
-
* Reading node_modules answers the real question and costs one `exists` call per package.
|
|
497
|
-
*/
|
|
498
|
-
function sdkPackagesPresent(framework, uiLibrary, io) {
|
|
499
|
-
const packages = frameworkPackages(framework, uiLibrary);
|
|
500
|
-
return (packages.length > 0 &&
|
|
501
|
-
packages.every((p) => io.exists(`${NODE_MODULES_DIR}/${p}/${PACKAGE_JSON}`)));
|
|
502
|
-
}
|
|
503
|
-
function applyEffects(plan, io) {
|
|
504
|
-
const failed = new Set();
|
|
505
|
-
const skipped = new Set();
|
|
506
|
-
const degraded = new Map();
|
|
507
|
-
let installFailed = false;
|
|
508
|
-
for (const s of plan.steps) {
|
|
509
|
-
if (s.status !== StepStatus.APPLY)
|
|
510
|
-
continue;
|
|
511
|
-
if (installFailed && true === s.dependsOnInstall) {
|
|
512
|
-
skipped.add(s.target);
|
|
513
|
-
continue;
|
|
514
|
-
}
|
|
515
|
-
const write = s.write;
|
|
516
|
-
if (write !== undefined) {
|
|
517
|
-
// Confirm the EFFECT, not the intention. Reported from the field (#160): init printed
|
|
518
|
-
// `[✓] Reticle config → .reticle.json` and the file was not there afterward. Nothing checked —
|
|
519
|
-
// no arrangement of the filesystem (a read-only mount, a full disk, an antivirus quarantining
|
|
520
|
-
// a new dotfile) could have turned that tick into anything else. A checkmark that cannot fail
|
|
521
|
-
// is decoration, and this one is the first thing a new user reads. Same shape as #139.
|
|
522
|
-
const wrote = spanSync('init.write', { target: s.target, path: write.path }, () => {
|
|
523
|
-
try {
|
|
524
|
-
// Format connect modules with the project's Prettier when present (#684) — a clean
|
|
525
|
-
// install must not fail the project's own lint on a file we just wrote.
|
|
526
|
-
const content = formatGeneratedSource(write.content, write.path, io.cwd());
|
|
527
|
-
io.writeFile(write.path, content);
|
|
528
|
-
}
|
|
529
|
-
catch {
|
|
530
|
-
return false; // a throw is the loud version of the same failure
|
|
531
|
-
}
|
|
532
|
-
return io.exists(write.path);
|
|
533
|
-
});
|
|
534
|
-
if (!wrote) {
|
|
535
|
-
failed.add(s.target);
|
|
536
|
-
continue; // do not run this step's exec against a file that is not there
|
|
537
|
-
}
|
|
538
|
-
}
|
|
539
|
-
// Bound once so the traced call cannot need a `?? ''` fallback — a default there would turn a
|
|
540
|
-
// narrowing mistake into an empty command that silently "succeeds".
|
|
541
|
-
const exec = s.exec;
|
|
542
|
-
// Per-step, because the interesting part of init's wall-clock is WHICH step spent it: a
|
|
543
|
-
// package-manager install and a `claude mcp add` are both subprocesses, and one of them being
|
|
544
|
-
// slow is a completely different problem from the other.
|
|
545
|
-
if (exec !== undefined &&
|
|
546
|
-
!spanSync('init.exec', { target: s.target, command: exec.command }, () => io.exec(exec.command, exec.args))) {
|
|
547
|
-
// A weaker second attempt beats no install at all — but only when it is REPORTED, because the
|
|
548
|
-
// thing it gives up is the version pin that keeps SDK and daemon in step.
|
|
549
|
-
//
|
|
550
|
-
// Traced separately, and it is the reason init can take twice as long as it looks like it
|
|
551
|
-
// should: this is a SECOND full package-manager run, and until it had its own span 1.6 of
|
|
552
|
-
// init's 2.3 seconds simply vanished — the span above accounted for the first attempt and
|
|
553
|
-
// nothing accounted for this one.
|
|
554
|
-
const retry = s.retry;
|
|
555
|
-
if (retry !== undefined &&
|
|
556
|
-
spanSync('init.exec.retry', { target: s.target, command: retry.command }, () => io.exec(retry.command, retry.args))) {
|
|
557
|
-
degraded.set(s.target, retry.note);
|
|
558
|
-
continue;
|
|
559
|
-
}
|
|
560
|
-
failed.add(s.target);
|
|
561
|
-
// A failed install only blocks the wiring when the packages are genuinely ABSENT. See
|
|
562
|
-
// sdkPackagesPresent: the guard protects "the import resolves", not "our subprocess exited 0".
|
|
563
|
-
if (s.target === DEPS_TARGET && !sdkPackagesPresent(plan.framework, plan.uiLibrary, io))
|
|
564
|
-
installFailed = true;
|
|
565
|
-
}
|
|
566
|
-
}
|
|
567
|
-
// Where this project lives, remembered for a daemon that will be started somewhere else.
|
|
568
|
-
//
|
|
569
|
-
// Deliberately AFTER the loop and unconditional on which steps ran: most init runs report "already
|
|
570
|
-
// wired", and a re-run in a re-cloned or moved checkout is exactly when the remembered path has
|
|
571
|
-
// gone stale — so the run that would otherwise be a no-op is the one that repairs the entry. The
|
|
572
|
-
// return value is ignored on purpose; this is a cache, and a read-only home directory must not
|
|
573
|
-
// turn a wired project into a failed init.
|
|
574
|
-
const projectId = projectIdOf(io.readFile(RETICLE_CONFIG_FILE));
|
|
575
|
-
if (projectId !== undefined) {
|
|
576
|
-
rememberProjectOnDisk(io, projectId, io.cwd(), Date.now());
|
|
577
|
-
}
|
|
578
|
-
return { failed, skipped, degraded };
|
|
579
|
-
}
|
|
580
|
-
/**
|
|
581
|
-
* Which STEP failed, from the step targets — not from an error string, which would carry paths.
|
|
582
|
-
*
|
|
583
|
-
* The distinction that matters: a dependency install failing is a machine/network problem (offline,
|
|
584
|
-
* a locked registry, a broken package manager), while MCP registration failing means the `claude` CLI
|
|
585
|
-
* is missing or refused. Two completely different fixes, and until now both were simply "init didn't
|
|
586
|
-
* work" with nothing to tell them apart.
|
|
587
|
-
*/
|
|
588
|
-
function classifyInitFailure(failed) {
|
|
589
|
-
if (failed.has(DEPS_TARGET))
|
|
590
|
-
return InitFailure.DEPENDENCY_INSTALL;
|
|
591
|
-
if (failed.has(MCP_TARGET))
|
|
592
|
-
return InitFailure.MCP_REGISTRATION;
|
|
593
|
-
return InitFailure.OTHER;
|
|
594
|
-
}
|
|
595
|
-
const AMBIGUOUS_HEADER = 'Several apps found in this workspace. Re-run `reticle init` inside the one you want:';
|
|
596
|
-
/**
|
|
597
|
-
* When the current directory is a workspace root with no app of its own, wire the app instead of the
|
|
598
|
-
* root. One candidate is wired silently (there is nothing to ask about); several are listed, because
|
|
599
|
-
* guessing which app someone meant is worse than one line of output.
|
|
600
|
-
* Returns null when there is nothing to redirect to — the caller then proceeds here as before.
|
|
601
|
-
*/
|
|
602
|
-
function redirectToWorkspaceApp(options, io, pkg) {
|
|
603
|
-
if (true === options.redirected)
|
|
604
|
-
return null;
|
|
605
|
-
const rootFiles = new Set(io.rootFiles());
|
|
606
|
-
const here = detect({
|
|
607
|
-
pkg: 'object' === typeof pkg && pkg !== null ? pkg : {},
|
|
608
|
-
configFiles: rootFiles,
|
|
609
|
-
lockfiles: new Set(),
|
|
610
|
-
});
|
|
611
|
-
// `--app` is an INSTRUCTION, and it is read before the guess below. The guess answers "where is
|
|
612
|
-
// the app?" for somebody who did not say; when somebody said, there is nothing left to infer.
|
|
613
|
-
//
|
|
614
|
-
// It used to be read after, and the check underneath returns early for any directory that looks
|
|
615
|
-
// like an app — which a JS monorepo ROOT does, because shared tooling puts `vite` in its
|
|
616
|
-
// devDependencies. So on a real pnpm+turbo monorepo (measured on nuclear, a Tauri v2 app at
|
|
617
|
-
// product scale) `reticle init --app packages/player` silently ignored the flag, installed the
|
|
618
|
-
// SDK into the root's package.json, wrote `.reticle.json` and a whole `src/reticle-dev.ts` into a
|
|
619
|
-
// repository root that has no `src/`, left `packages/player` untouched — and reported three ✓ and
|
|
620
|
-
// one ⚠. The one flag documented for this shape wired the wrong directory and said it worked.
|
|
621
|
-
//
|
|
622
|
-
// Existence is the test, not membership of the discovered list: discovery scans conventional
|
|
623
|
-
// directories, and somebody who names a path knows their own layout better than the scan does.
|
|
624
|
-
const named = options.app === undefined || '' === options.app ? undefined : options.app;
|
|
625
|
-
if (named !== undefined) {
|
|
626
|
-
const wanted = named.replace(/\/+$/, '');
|
|
627
|
-
if (io.exists(`${wanted}/${PACKAGE_JSON}`))
|
|
628
|
-
return enterApp(options, io, wanted, 'Wiring');
|
|
629
|
-
io.print(`--app ${named} does not name a directory with a package.json in it.`);
|
|
630
|
-
return { ok: false, applied: 0, manual: 1 };
|
|
631
|
-
}
|
|
632
|
-
if (here.framework !== Framework.HTML)
|
|
633
|
-
return null; // this directory IS the app
|
|
634
|
-
const apps = findWorkspaceApps(io);
|
|
635
|
-
// An explicitly named app answers the ambiguity. Refusing to guess is right, but "re-run inside the
|
|
636
|
-
// one you want" is not something a script, a CI step, or an agent that cannot change directory can
|
|
637
|
-
// act on — so the refusal was a dead end for exactly the callers most likely to hit it.
|
|
638
|
-
const chosen = chooseWorkspaceApp(options.app, apps);
|
|
639
|
-
if (!chosen.ok) {
|
|
640
|
-
io.print(chosen.message);
|
|
641
|
-
return { ok: false, applied: 0, manual: 1 };
|
|
642
|
-
}
|
|
643
|
-
const target = chosen.app ?? (1 === apps.length ? apps[0] : undefined);
|
|
644
|
-
if (target === undefined) {
|
|
645
|
-
if (0 === apps.length)
|
|
646
|
-
return null; // not a workspace — fall through to the normal HTML plan
|
|
647
|
-
io.print(AMBIGUOUS_HEADER);
|
|
648
|
-
for (const a of apps)
|
|
649
|
-
io.print(` ${a}`);
|
|
650
|
-
io.print('');
|
|
651
|
-
io.print(`Or name one without changing directory: reticle init --app ${apps[0] ?? '<dir>'}`);
|
|
652
|
-
return { ok: false, applied: 0, manual: apps.length };
|
|
653
|
-
}
|
|
654
|
-
return enterApp(options, io, target, 'No app in this directory — wiring');
|
|
655
|
-
}
|
|
656
|
-
/**
|
|
657
|
-
* Re-enter `init` scoped to one directory of a workspace.
|
|
658
|
-
*
|
|
659
|
-
* One implementation for both routes in — the app somebody NAMED and the app discovery found when
|
|
660
|
-
* nobody did. They differ only in the sentence printed; scoping the io, moving the cwd and keeping
|
|
661
|
-
* the agent root has to be identical for both, and was worth having twice for exactly as long as it
|
|
662
|
-
* took to get one of them wrong.
|
|
663
|
-
*/
|
|
664
|
-
function enterApp(options, io, target, lead) {
|
|
665
|
-
io.print(`${lead} ${target}.`);
|
|
666
|
-
io.print('');
|
|
667
|
-
return runInit({
|
|
668
|
-
...options,
|
|
669
|
-
cwd: join(options.cwd, target),
|
|
670
|
-
redirected: true,
|
|
671
|
-
// Where the human is STANDING, kept across the redirect: their agent session runs here, so
|
|
672
|
-
// this is the only place a `/reticle` command file can be found by it.
|
|
673
|
-
agentRoot: options.agentRoot ?? options.cwd,
|
|
674
|
-
}, io.scoped(target));
|
|
675
|
-
}
|
|
676
|
-
export function runInit(options, io) {
|
|
677
|
-
const result = runInitSteps(options, io);
|
|
678
|
-
// The ask goes here, not in report(): report() is only the success-shaped path, and the exits that
|
|
679
|
-
// matter most are the ones that never reach it — no package.json, an ambiguous workspace — where
|
|
680
|
-
// setup died before anything ran and the person holding the report has the least to go on. This
|
|
681
|
-
// wrapper is the one point every exit passes through.
|
|
682
|
-
//
|
|
683
|
-
// `redirected` is what keeps it to ONE print: wiring an app in a monorepo re-enters runInit for the
|
|
684
|
-
// chosen directory, and the inner call must not ask again.
|
|
685
|
-
if (true !== options.redirected) {
|
|
686
|
-
io.print('');
|
|
687
|
-
io.print(FEEDBACK_HINT);
|
|
688
|
-
}
|
|
689
|
-
return result;
|
|
690
|
-
}
|
|
691
|
-
/**
|
|
692
|
-
* The manifest, parsed once, or the reason it could not be.
|
|
693
|
-
*
|
|
694
|
-
* It used to be parsed in three places from the same raw string, and two of them were unguarded —
|
|
695
|
-
* so `reticle init` on a `package.json` with a trailing comma died with a raw `SyntaxError` and a
|
|
696
|
-
* stack through `redirectToWorkspaceApp`. A stack trace in front of a user is a bug whatever caused
|
|
697
|
-
* it, and this one lands on the very first thing the command does, before it has said anything.
|
|
698
|
-
*
|
|
699
|
-
* `setup/reticle.mjs` has always got this right ("… is not valid JSON (…). Fix it and re-run"). The
|
|
700
|
-
* shipped CLI did not, which is the shape of every divergence between the two: the prototype refuses
|
|
701
|
-
* politely, `init` throws. Parsing once at the single point the file enters means no later caller
|
|
702
|
-
* CAN reintroduce it — a guard per call site would have been three guards and a fourth one waiting.
|
|
703
|
-
*/
|
|
704
|
-
function readManifest(io) {
|
|
705
|
-
const raw = io.readFile(PACKAGE_JSON);
|
|
706
|
-
if (null === raw)
|
|
707
|
-
return { pkg: null };
|
|
708
|
-
try {
|
|
709
|
-
return { pkg: JSON.parse(raw) };
|
|
710
|
-
}
|
|
711
|
-
catch (err) {
|
|
712
|
-
// First line only: JSON.parse's message carries the offending position, and the rest is noise.
|
|
713
|
-
const detail = String(err instanceof Error ? err.message : err).split('\n')[0] ?? 'unparseable';
|
|
714
|
-
return { error: detail };
|
|
715
|
-
}
|
|
716
|
-
}
|
|
717
|
-
function runInitSteps(options, io) {
|
|
718
|
-
const manifest = readManifest(io);
|
|
719
|
-
if ('error' in manifest) {
|
|
720
|
-
io.print(`${PACKAGE_JSON} is not valid JSON (${manifest.error}). Fix it and re-run — init reads the ` +
|
|
721
|
-
'framework, the dev script and the package manager from it, and will not guess at any of ' +
|
|
722
|
-
'them from a file it cannot read.');
|
|
723
|
-
reportInitOutcome({ ok: false, reason: InitFailure.MALFORMED_PACKAGE_JSON });
|
|
724
|
-
return { ok: false, applied: 0, manual: 0 };
|
|
725
|
-
}
|
|
726
|
-
const pkgRaw = manifest.pkg;
|
|
727
|
-
// Look for the app BEFORE concluding there isn't one.
|
|
728
|
-
//
|
|
729
|
-
// A root with no package.json is not a dead end — it is the ordinary shape of a repo whose app
|
|
730
|
-
// lives one directory down (`frontend/`, `web/`, `client/`) with no manifest at the top. Bailing
|
|
731
|
-
// first made that repo un-instrumentable: `reticle init` said "No package.json found", and
|
|
732
|
-
// `--app frontend`, the flag that exists for exactly this, was never read because the bail came
|
|
733
|
-
// first. Reported twice by the same user, who tried the documented workaround and hit the same
|
|
734
|
-
// wall. Discovery already handles the case (it scans top-level directories, not just declared
|
|
735
|
-
// workspaces); it was simply unreachable.
|
|
736
|
-
//
|
|
737
|
-
// `'{}'` because the redirect only needs the manifest to ask "is THIS directory the app", and a
|
|
738
|
-
// directory with no package.json is definitively not.
|
|
739
|
-
const redirectedEarly = redirectToWorkspaceApp(options, io, pkgRaw ?? {});
|
|
740
|
-
if (redirectedEarly !== null)
|
|
741
|
-
return redirectedEarly;
|
|
742
|
-
if (null === pkgRaw) {
|
|
743
|
-
io.print(
|
|
744
|
-
// Two genuinely different situations used to share one sentence: a JS developer in the wrong
|
|
745
|
-
// directory, and a project that is not JavaScript at all. The second reads the old wording as
|
|
746
|
-
// a path problem and goes looking for a directory that cannot exist — reported from a
|
|
747
|
-
// Streamlit app, where the search continued into hunting for a browser bundle to inject by
|
|
748
|
-
// hand before the real answer surfaced.
|
|
749
|
-
noPackageJsonMessage((file) => io.exists(join(options.cwd, file))));
|
|
750
|
-
// The message says "add the snippet below". Print the snippet, or the message is the same
|
|
751
|
-
// broken promise in the other direction. `connectArg` carries the port; there is no projectId
|
|
752
|
-
// to bake, because a projectId is derived from the package.json that does not exist here.
|
|
753
|
-
io.print(staticPageSnippet(connectArgWithToken(options.port, undefined, readPairingToken())));
|
|
754
|
-
// The onboarding funnel had NO instrumentation, so a setup that died here was indistinguishable
|
|
755
|
-
// from someone who never ran the command — the two failure modes with the most different fixes.
|
|
756
|
-
reportInitOutcome({ ok: false, reason: InitFailure.NO_PACKAGE_JSON });
|
|
757
|
-
return { ok: false, applied: 0, manual: 0 };
|
|
758
|
-
}
|
|
759
|
-
// Init is the flow a user experiences the wait of personally, and the fixture gate measures it at
|
|
760
|
-
// 1–6s per app with no explanation of the spread. These three spans split that number into detect
|
|
761
|
-
// (filesystem probing), plan (pure), and apply (writes + package-manager and CLI subprocesses).
|
|
762
|
-
const planInput = gatherPlanInput(options, io, pkgRaw);
|
|
763
|
-
// Before anything is written, and AFTER detection — the package manager to check is the one init
|
|
764
|
-
// RESOLVED, not a raw lockfile read. An inherited pnpm-lock.yaml at a monorepo root does not mean
|
|
765
|
-
// the app in frontend/ uses pnpm, and checking the file refused a scaffold the install gate proves
|
|
766
|
-
// must succeed. Both conditions make every later phase fail, and each arrives far from its cause
|
|
767
|
-
// when it is not checked here. See preflight.ts.
|
|
768
|
-
const refusal = preflightRefusal({
|
|
769
|
-
cwd: () => io.cwd(),
|
|
770
|
-
canWrite: () => io.canWrite(),
|
|
771
|
-
probe: (command, args) => io.probe(command, args),
|
|
772
|
-
}, planInput.detection.packageManager);
|
|
773
|
-
if (refusal !== undefined) {
|
|
774
|
-
io.print(refusal);
|
|
775
|
-
return { ok: false, applied: 0, manual: 1 };
|
|
776
|
-
}
|
|
777
|
-
const plan = spanSync('init.plan', {}, () => buildPlan(planInput));
|
|
778
|
-
const effects = options.dryRun
|
|
779
|
-
? { failed: new Set(), skipped: new Set(), degraded: new Map() }
|
|
780
|
-
: spanSync('init.apply', { steps: plan.steps.length }, () => applyEffects(plan, io));
|
|
781
|
-
const { failed, skipped, degraded } = effects;
|
|
782
|
-
// The project's own dev command, so the closing line names what a human would actually type.
|
|
783
|
-
const devCommand = devCommandFrom(pkgRaw, planInput.detection.packageManager);
|
|
784
|
-
const result = report(plan, options.dryRun, failed, skipped, degraded, io, options.cwd, agentRootOf(options), devCommand, true === options.continuesToRuntime);
|
|
785
|
-
// A dry run is a preview, not an outcome — reporting it would inflate both success and failure.
|
|
786
|
-
if (options.dryRun)
|
|
787
|
-
return result;
|
|
788
|
-
const outcome = {
|
|
789
|
-
ok: result.ok,
|
|
790
|
-
...(result.ok ? {} : { reason: classifyInitFailure(failed) }),
|
|
791
|
-
stack: plan.framework,
|
|
792
|
-
// The step's REAL final status, not the absence of a failure — see mcp-registered.
|
|
793
|
-
mcpRegistered: wasMcpRegistered(resolvedStatus(plan, MCP_TARGET, failed, skipped)),
|
|
794
|
-
};
|
|
795
|
-
const context = {
|
|
796
|
-
appDir: options.cwd,
|
|
797
|
-
framework: plan.framework,
|
|
798
|
-
packageManager: planInput.detection.packageManager,
|
|
799
|
-
...(undefined === devCommand ? {} : { devCommand }),
|
|
800
|
-
...(true === options.redirected ? { redirectedTo: options.cwd } : {}),
|
|
801
|
-
};
|
|
802
|
-
if (true === options.deferOutcome)
|
|
803
|
-
return { ...result, context, outcome };
|
|
804
|
-
reportInitOutcome(outcome);
|
|
805
|
-
return { ...result, context };
|
|
806
|
-
}
|