Skip to content

Route-Share Local Devstack

Issue: #1052. Parent: #1041.

The reset needs one local loop an agent can run before a product branch is merge-ready. This command makes that loop explicit: service paths, ports, commands, fake Garmin, fake chat, local infra, and smoke evidence all live in one machine-readable plan.

Commands

imperfect-cli route-share-devstack check
imperfect-cli route-share-devstack up --smoke
imperfect-cli route-share-devstack up --smoke --exit-after-smoke
imperfect-cli route-share-devstack smoke

check does not start services or mutate local infra. It prints the full plan, optionally writes route-share-devstack-check.json with --artifact-dir, and exits 2 with actionable missing inputs when a sibling repo, command, compose file, or local env is missing.

Preflight expects docker, uv, node, and npm on PATH; sibling repos alice, cheshire, footman, and looking-glass under --repos-root / ROUTE_SHARE_REPOS_ROOT; and either an imperfect-api .env file or ANTHROPIC_API_KEY in the shell for local imports.

up starts local infra from docker-compose.route-share.yml, then starts the hot-reload service processes. Use --exit-after-smoke for a bounded agent/CI run that boots, validates, then cleans up instead of staying attached:

service URL command
imperfect-api http://127.0.0.1:8000 uv run imperfect-cli serve --port 8000
Cheshire http://127.0.0.1:8767 uv run uvicorn cheshire.server:app --host 127.0.0.1 --port 8767
Alice http://127.0.0.1:8766 docker compose up --build -d alice
fake Garmin http://127.0.0.1:8010 uv run uvicorn alice.vendors.garmin.fake_server:create_fake_garmin_app --factory --host 127.0.0.1 --port 8010
Footman fake chat http://127.0.0.1:4510 node src/cli.js whatsapp route-share-sim --host 127.0.0.1 --port 4510
Looking Glass http://127.0.0.1:3000 npm run dev -- --hostname 127.0.0.1 --port 3000

For agent runs, pass --artifact-dir tmp/route-share-devstack to persist route-share-devstack-check.json and route-share-devstack-smoke.json. Host-process logs are appended under tmp/route-share-devstack/logs/{service}.log. If health checks time out, retry with --timeout-seconds 90 before treating it as a product failure.

The default Cheshire path is the normal sibling cheshire checkout. #980 is merged, so the devstack no longer prefers the old #977 issue worktree; using main by default avoids stale WIP winning silently. Override sibling discovery with:

ROUTE_SHARE_REPOS_ROOT=/path/to/repos imperfect-cli route-share-devstack check

When a service runs from a linked worktree, the devstack also checks the canonical sibling checkout for a local .env file and passes those values to the child process. The printed plan includes hard-coded local dev env, env-file paths, and required key names; dotenv secret values are not printed. Cheshire currently requires MAPTILER_API_KEY before it can boot because its settings validate raster tile configuration at startup.

Local Infra

docker-compose.route-share.yml starts the infra the route-share harness owns:

  • MongoDB on host port 27018, database cuishe_route_share
  • MinIO on 9000 with bucket imperfect-route-shares-local

Alice's own compose file still owns Postgres + Valkey on 6379, under the route-share-specific Compose project route-share-e2e-alice. Cheshire's own compose file starts Postgres on 5433 and Valkey on 6380 under route-share-e2e-cheshire, while the Cheshire ASGI process stays host-side for fast local iteration. The route-share stack injects fake Garmin endpoint env into Alice and local R2 env into Cheshire. The fake Garmin OAuth callback is allowlisted for http://127.0.0.1:3000/garmin/route-send/callback, and imperfect-api sends WhatsApp bridge deliveries to the fake chat adapter on 4510. No production Garmin credentials are required for the local path.

Alice points FIREBASE_CREDENTIALS_PATH at an intentionally missing file in this stack. Alice treats missing Firebase credentials as degraded Apple Health auth, while the Garmin internal routes used by route-share e2e continue to boot. This avoids Docker's missing-file bind mount behavior, where FIREBASE_CREDENTIALS.json can appear as a directory and crash startup.

Detached services are readiness-gated before dependent host processes start: Alice must answer /health before the devstack launches Cheshire, because Cheshire introspects Alice's migrated vendor schemas during startup.

The backing stores are intentionally ephemeral: cleanup runs docker compose down --volumes --remove-orphans for the route-share, Alice, and Cheshire Compose projects. That keeps local e2e runs deterministic and avoids stale schema/data from previous route-share experiments.

Smoke

smoke validates the running fake services and the fake-chat contract payload:

  • GET /api/contract and GET /api/fixture from the Footman fake chat
  • GET /fake-garmin/state from Alice's fake Garmin
  • the fake-chat payload through assert_route_share_reset_route_options_payload()

The route-option smoke rejects:

  • media attachments / HD PNG delivery
  • text baked into preview images
  • oversized, unowned, or non-JPG preview assets
  • legacy picker copy from the retired two-turn flow
  • replies that cannot bind back to a single route option message

This proves the services boot, local fake chat exposes the route-option contract shape, and fake Garmin is reachable. It does not prove live route generation. To run live mode, start imperfect-cli route-share-devstack up --smoke in one terminal and leave it running. Then run the live harness from a second terminal:

imperfect-cli route-share-e2e --live \
  --prompt "Create three scenic trail running routes from the Conservatory of Flowers in Golden Gate Park, San Francisco, about 5 miles, starting and ending there." \
  --artifact-dir tmp/route-share-e2e-live

That live command is wired through fake chat, the BFF webhook, channel workers, route-page HTML rendering, manifest checks, fake Garmin auth/callback, and duplicate send idempotency. Generated local runs should produce artifacts that point at the current product contract instead of forcing manual WhatsApp or Garmin testing. Browser screenshots and a real WhatsApp unfurl smoke remain the next layer above this local loop.