B4. The Mac picks a GitHub repository for each plan, and the Mac folder path is deleted

Merged
DA
11d ago
+2710-1420

B4. The Mac picks a GitHub repository for each plan, and the Mac folder path is deleted

Stacked on coding-agents; B3 (#1868) is merged beneath it.

The repository chip

  • HostedPlanClient gains repositories() (GET /api/github/repositories) and setRepository() (PATCH /api/plans/{id} with {repository}), and create() now answers the service's repository-not-reachable and github-sign-in-required refusals as the failures they are, beside unanswered.
  • The host's planning composer answers two new Gateway methods, planning.repositories and planning.setRepository, and planning.start takes the plan's repository. A plan given its repository is drawn on the list's row and the open document as the service answered, nothing read again.
  • The desktop's new-plan composer's folder chip is now a repository chip (renderer/planning/repository-chip.tsx): the GitHub mark and owner/name, starting on the repository the newest plan was about. Its menu lists the recent repositories, then Search all repositories… (filters the service's list), then Choose which repositories Luke can see ↗. With the App installed nowhere, the chip reads Install Luke on GitHub ↗ and opens the installation page directly. Arrows, Escape, and focus leaving behave as the folder menu did. Refusals show under the card.
  • The list is read when the chip mounts and again whenever the window takes focus, which is how it picks up an App installed in the browser; nothing polls.
  • A plan with no repository (the 13 old folder plans among them) opens on the same chip, under the title in the toolbar; a pick is saved through the PATCH and a refusal said beside the chip. A plan can be used without a repository.
  • The plan menu's Reveal in Finder / Change folder… become Change repository… (opens the chip's menu, opening the plan first where it is not the open one) and Open on GitHub (https://github.com/<owner>/<name>, through a github.open act whose schema admits GitHub's addresses alone). The ⌥⌘R shortcut and its menu item are gone. The sidebar's rows and the plan header show owner/repository.

The Mac folder path, deleted

plan-folders.json and planFoldersFile, PlanningView.folders, planning.setFolder, the planning.chooseFolder / planning.setFolder / planning.revealFolder acts, the system folder dialog and shell.showItemInFolder in register-desktop-ipc.ts, folderLine / folderName / recentFolders, the folder glyphs, and their tests.

The Code tab now reads from the sandbox (agreed with the orchestrator)

The Code tab was fed by show_code, whose lines the Mac read from the plan's folder on disk, so it had to move with the folder:

  • show_code (apps/web/server/hosted/show-code.ts) now reads the lines itself, from the planning session's checkout of the plan's repository, through the shell's helpers (checkedOutSandbox, runInSandbox): the file probed (resolved inside the checkout root, a regular file, under 1 MiB, text) and then a window of at most 200 lines around the lines pointed at, each cut to 400 characters (SHOWN_CODE_BOUNDS). The path reaches the scripts as a variable, never as shell text; a .env file is refused before any read.
  • The lines are the call's answer, so they are journaled with the call; projectTurnEvents tells code_shown once the call has answered, carrying the ShownCode (ref, repository, firstLine, lineCount, lines), and the plan.code frame carries it to the Mac. A missing, binary, secret, or unreachable file answers rejected with why, and nothing goes on screen: nothing unread is described as shown.
  • repository-shell.ts's refusal reasons are now worded once (REPOSITORY_REFUSAL) with each tool's verb ahead of them (Not run: / Not shown:); REPOSITORY_SHELL_REFUSAL's values are unchanged.
  • The Mac's plan-code.ts is now a pure highlighter over the lines that arrived; the disk read, CODE_UNREADABLE, and the pane's unreadable states are gone. The pane names the repository beside the path.
  • Goldens: live-contract-planCodeFrameSchema.json re-recorded for the new frame (code in place of ref), and plan-wire-shownCodeSchema.json added.

Docs

docs/PLANNING.md (New plan, Header with the plan menu, Side panel, How the planning model reads code, the journey, the acts row) and PRIVACY.md's "Code on screen during a planning call" (the code now comes from the service's checkout of the GitHub repository, not from the Mac's disk).

Tests

Hermetic throughout: the client methods (plan-client.test.ts), the host's repository methods and the sent code (compose-planning.test.ts, plan-code.test.ts), the acts (planning-acts.test.ts, acts.test.ts), the chip's states — installed, not installed, empty, refusal, search, keyboard — and the composer (new-plan-form.test.ts), the null-repository plan and the menu actions (plan-actions.test.ts, use-plans-tab.test.ts), the server read from a sandbox double (show-code.test.ts), the projection (hosted-turn-events.test.ts), and the voice package's frames.

./scripts/check.sh exits 0. CI could not verify the Mac: verify.sh was waived for this rollout.

Follow-ups, not in this PR

  • packages/hosted/CLAUDE.md's "A turn's events are a projection" still says the queued question is "the one call whose words are told"; shown code's lines are now told too. Left for Dean, since the agent guides are not edited without approval.

🤖 Generated with Claude Code