Read a local folder on the Mac instead of a GitHub clone for plans

Merged
CH
16d ago
+1415-1426

Plans needed a GitHub OAuth connection: the service resolved the repository through it, and run_in_repository ran bash in a Vercel sandbox clone. This PR changes a plan to name a folder on the developer's Mac, and runs the planning model's commands there.

CI could not verify the Mac side. No verify.sh run yet. The desktop loop, the folder picker, and the form are tested with fakes and a real child process on Linux only.

How a command travels

  1. The model calls run_in_repository({ command }). The tool inserts a plan_command row and reads it every 250 ms, for up to 60 s (apps/web/server/hosted/repository-shell.ts).
  2. While a plan is open, the Mac long-polls POST /api/plans/{id}/commands/claim. The service holds the request up to 20 s and claims the oldest unclaimed row for the caller's plan.
  3. The Mac runs bash -lc <command> in the plan's folder, which only the Mac knows (30 s timeout, output cut to 20,000 characters each) and posts the result to POST /api/plans/{id}/commands/{commandId} (packages/host/src/planning-commands.ts).
  4. The tool answers the model the exit code, stdout, and stderr. If no answer arrives in 60 s, it answers not-run.

The tool keeps its name and its { command } input, so the voice activity line, the REPOSITORY_READ slow step, and the voice instructions are unchanged.

Changes

  • Data: migration 0054_plan_command.sql drops repository_owner/name/branch/commit and adds the plan_command table. The service stores no folder path or name for a plan.
  • Wire: planRepositorySchema is gone, and a plan is its name and document. The claim and result schemas are new.
  • Server: plan creation no longer calls GitHub. apps/web/eve/sandbox.ts, cloneRepository, and the bash and githubAccess brain-host seams are deleted.
  • Mac: each plan's folder is kept in plan-folders.json in the state root (planFoldersFile in compose-planning.ts) and carried to the renderer as the planning view's folders. The new-plan form and the planning.chooseFolder act open the system folder picker. A plan with no folder on this Mac shows "Choose folder…" (planning.setFolder), and its commands answer that no folder is chosen. Plan rows and headers show the path, with ~ for the home folder.
  • GitHub sign-in: asks for read:user and user:email only; repo is dropped (auth-policy.ts).
  • PRIVACY.md: the GitHub sandbox lines are replaced by "How the planning model reads your folder".
  • GitHub code: the repository list and Connect GitHub acts stay in place, unused by plans. The knip check (unused exports) required deleting resolveRepository and noGitHubConnections and making githubFailureResponse private.

Goldens

The JSON Schema goldens under packages/hosted/fixtures/json-schema/ were re-recorded with LUKE_UPDATE_FIXTURES=1:

  • plan-wire-planRepositorySchema is removed.
  • The plan summary, plan, list, answer, and create-request goldens drop repository and hold no folder.
  • Four new command goldens are added.

Not in this PR

  • docs/PLANNING.md still describes the GitHub flow.

Testing

  • ./scripts/check.sh exits 0.
  • pnpm --filter @luke/web test:store: 76 files pass.
  • New tests:
    • repository-shell.test.ts: claim, settle, and answer; the deadline answers not-run; a command is claimed once and never by another account.
    • plans-app.test.ts: the claim and settle routes.
    • compose-planning.test.ts: the open plan's command runs in a temp folder and its exit code, stdout, and stderr come back.
    • planning-acts.test.ts: the folder picker act.
  • To test on a Mac: run ./scripts/run.sh, start a plan on a local folder, answer one question, and check that "Planning model · " shows and the model names real files from the folder.

🤖 Generated with Claude Code


Open workspace in Conductor