|
| 1 | +# Apple Container Backend Implementation Plan |
| 2 | + |
| 3 | +> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. |
| 4 | +
|
| 5 | +**Goal:** Add Apple Container (`container` CLI) support for `ggo studio create` on macOS 26+, with correct auto-selection, install/upgrade messaging, and updated CLI/docs/UI strings. |
| 6 | + |
| 7 | +**Architecture:** Introduce platform helpers for macOS version and Docker socket detection, then update backend selection to prefer Apple Container only when no Docker socket is present on macOS 26+. Rebuild the Apple backend around the `container` CLI with JSON parsing for list/inspect, and ensure install/runtime hints are precise. |
| 8 | + |
| 9 | +**Tech Stack:** Go, Cobra, Ginkgo/Gomega, Apple `container` CLI. |
| 10 | + |
| 11 | +--- |
| 12 | + |
| 13 | +### Task 1: Add Ginkgo suite + failing tests for macOS selection rules |
| 14 | + |
| 15 | +**Files:** |
| 16 | +- Create: `internal/studio/studio_suite_test.go` |
| 17 | +- Create: `internal/studio/manager_apple_ginkgo_test.go` |
| 18 | + |
| 19 | +**Step 1: Write the failing test** |
| 20 | +```go |
| 21 | +var _ = Describe("Apple container selection", func() { |
| 22 | + It("prefers apple-container on macOS 26+ when no Docker socket is present", func() { |
| 23 | + // stub platform helpers to darwin/26/no-socket |
| 24 | + // register apple + docker backends (both available) |
| 25 | + // expect ModeAppleContainer for ModeAuto |
| 26 | + }) |
| 27 | + |
| 28 | + It("prefers docker/colima when Docker socket exists on macOS 26+", func() { |
| 29 | + // stub platform helpers to darwin/26/has-socket |
| 30 | + // register apple + docker backends (both available) |
| 31 | + // expect ModeDocker for ModeAuto |
| 32 | + }) |
| 33 | + |
| 34 | + It("rejects apple-container on macOS < 26 when explicitly requested", func() { |
| 35 | + // stub platform helpers to darwin/25 |
| 36 | + // expect error mentioning macOS 26 upgrade |
| 37 | + }) |
| 38 | +}) |
| 39 | +``` |
| 40 | + |
| 41 | +**Step 2: Run test to verify it fails** |
| 42 | +Run: `go test ./internal/studio -run Apple` |
| 43 | +Expected: FAIL with missing helper stubs / selection logic not implemented |
| 44 | + |
| 45 | +**Step 3: Write minimal implementation** |
| 46 | +Implement platform helper stubs and selection logic hooks (function vars) to satisfy the tests. |
| 47 | + |
| 48 | +**Step 4: Run test to verify it passes** |
| 49 | +Run: `go test ./internal/studio -run Apple` |
| 50 | +Expected: PASS |
| 51 | + |
| 52 | +**Step 5: Commit** |
| 53 | +```bash |
| 54 | +git add internal/studio/studio_suite_test.go internal/studio/manager_apple_ginkgo_test.go |
| 55 | +GIT_OPTIONAL_LOCKS=0 git -c core.hooksPath=/dev/null commit -m "test: add apple-container selection tests" |
| 56 | +``` |
| 57 | + |
| 58 | +### Task 2: Implement macOS version + Docker socket helpers and update Manager selection/hints |
| 59 | + |
| 60 | +**Files:** |
| 61 | +- Create: `internal/platform/macos_version_darwin.go` |
| 62 | +- Create: `internal/platform/macos_version_other.go` |
| 63 | +- Create: `internal/platform/docker_socket.go` |
| 64 | +- Modify: `internal/studio/manager.go` |
| 65 | + |
| 66 | +**Step 1: Write the failing test** |
| 67 | +Extend existing Ginkgo tests (Task 1) to assert explicit error messaging for unsupported macOS version and missing Apple Container install guidance. |
| 68 | + |
| 69 | +**Step 2: Run test to verify it fails** |
| 70 | +Run: `go test ./internal/studio -run Apple` |
| 71 | +Expected: FAIL with error text mismatch or missing helpers |
| 72 | + |
| 73 | +**Step 3: Write minimal implementation** |
| 74 | +- Add `platform.MacOSMajorVersion()` using `syscall.Sysctl("kern.osproductversion")` on darwin; return `0` elsewhere. |
| 75 | +- Add `platform.HasDockerSocket()` to scan `DOCKER_HOST`, `/var/run/docker.sock`, `~/.colima/*/docker.sock`, `~/.orbstack/run/docker.sock`. |
| 76 | +- Update `Manager.detectBestBackend` to: |
| 77 | + - On darwin 26+: prefer apple-container only when no Docker socket is found. |
| 78 | + - On darwin < 26: exclude apple-container from auto preference list. |
| 79 | +- Update `platformBackendHint` to include install guidance: |
| 80 | + - Apple Container: download pkg from GitHub releases. |
| 81 | + - Colima/OrbStack: `brew install`. |
| 82 | + - Docker: link to Docker Desktop. |
| 83 | +- When `--mode apple-container` is requested on macOS < 26, return a clear upgrade error. |
| 84 | + |
| 85 | +**Step 4: Run test to verify it passes** |
| 86 | +Run: `go test ./internal/studio -run Apple` |
| 87 | +Expected: PASS |
| 88 | + |
| 89 | +**Step 5: Commit** |
| 90 | +```bash |
| 91 | +git add internal/platform/macos_version_darwin.go internal/platform/macos_version_other.go internal/platform/docker_socket.go internal/studio/manager.go |
| 92 | +GIT_OPTIONAL_LOCKS=0 git -c core.hooksPath=/dev/null commit -m "feat: add macOS version/socket helpers and selection rules" |
| 93 | +``` |
| 94 | + |
| 95 | +### Task 3: Rebuild Apple backend to use `container` CLI + parsing helpers |
| 96 | + |
| 97 | +**Files:** |
| 98 | +- Modify: `internal/studio/backend_apple.go` |
| 99 | +- Create: `internal/studio/apple_container_parse_test.go` |
| 100 | + |
| 101 | +**Step 1: Write the failing test** |
| 102 | +```go |
| 103 | +var _ = Describe("Apple container parsing", func() { |
| 104 | + It("maps container list JSON to Environment and SSH port", func() { |
| 105 | + // Provide sample JSON from `container list --format json` |
| 106 | + // Expect label filtering, SSH port extraction, GPU_WORKER_URL parsing |
| 107 | + }) |
| 108 | +}) |
| 109 | +``` |
| 110 | + |
| 111 | +**Step 2: Run test to verify it fails** |
| 112 | +Run: `go test ./internal/studio -run Apple` |
| 113 | +Expected: FAIL (parsing helpers not implemented) |
| 114 | + |
| 115 | +**Step 3: Write minimal implementation** |
| 116 | +- Replace docker CLI usage with `container` CLI subcommands: |
| 117 | + - `container system status` for availability |
| 118 | + - `container system start` in `EnsureRunning` |
| 119 | + - `container run --detach` for create |
| 120 | + - `container list --format json` for list |
| 121 | + - `container inspect` for get |
| 122 | + - `container exec`, `container logs`, `container start`, `container stop`, `container delete --force` |
| 123 | +- Add JSON parsing helpers for list/inspect output. |
| 124 | +- Normalize memory suffixes for `container` CLI (`Gi`->`G`, `Mi`->`M`). |
| 125 | +- Use labels `ggo.managed=true`, `ggo.name`, `ggo.mode=apple-container`. |
| 126 | + |
| 127 | +**Step 4: Run test to verify it passes** |
| 128 | +Run: `go test ./internal/studio -run Apple` |
| 129 | +Expected: PASS |
| 130 | + |
| 131 | +**Step 5: Commit** |
| 132 | +```bash |
| 133 | +git add internal/studio/backend_apple.go internal/studio/apple_container_parse_test.go |
| 134 | +GIT_OPTIONAL_LOCKS=0 git -c core.hooksPath=/dev/null commit -m "feat: implement apple-container backend via container CLI" |
| 135 | +``` |
| 136 | + |
| 137 | +### Task 4: Update CLI/Docs/UI strings for `apple-container` |
| 138 | + |
| 139 | +**Files:** |
| 140 | +- Modify: `internal/studio/types.go` |
| 141 | +- Modify: `cmd/ggo/studio/studio.go` |
| 142 | +- Modify: `docs/studio-guide.md` |
| 143 | +- Modify: `vscode-extension/src/views/createStudioPanel.ts` |
| 144 | + |
| 145 | +**Step 1: Write the failing test** |
| 146 | +(Documentation/UI change; no automated test required) |
| 147 | + |
| 148 | +**Step 2: Run test to verify it fails** |
| 149 | +Skip |
| 150 | + |
| 151 | +**Step 3: Write minimal implementation** |
| 152 | +- Replace `apple` mode string with `apple-container`. |
| 153 | +- Update CLI help, examples, and backend listing output to include install guidance. |
| 154 | +- Update Studio guide table and VS Code UI mode labels. |
| 155 | + |
| 156 | +**Step 4: Run test to verify it passes** |
| 157 | +Run: `go test ./cmd/ggo/studio ./internal/studio` |
| 158 | +Expected: PASS |
| 159 | + |
| 160 | +**Step 5: Commit** |
| 161 | +```bash |
| 162 | +git add internal/studio/types.go cmd/ggo/studio/studio.go docs/studio-guide.md vscode-extension/src/views/createStudioPanel.ts |
| 163 | +GIT_OPTIONAL_LOCKS=0 git -c core.hooksPath=/dev/null commit -m "docs: update apple-container mode strings" |
| 164 | +``` |
| 165 | + |
| 166 | +### Task 5: Full functional Apple Container CLI test (manual) |
| 167 | + |
| 168 | +**Files:** |
| 169 | +- None (manual verification) |
| 170 | + |
| 171 | +**Step 1: Run container services** |
| 172 | +Run: `container system start` |
| 173 | +Expected: services start successfully |
| 174 | + |
| 175 | +**Step 2: Run a common arm64 image** |
| 176 | +Run: `container run --name ggo-apple-test --detach --rm -p 18022:22/tcp alpine:latest sleep 600` |
| 177 | +Expected: container ID printed, `container list` shows it running |
| 178 | + |
| 179 | +**Step 3: Exec/logs/stop/delete** |
| 180 | +Run: |
| 181 | +- `container exec ggo-apple-test sh -c "echo ok"` |
| 182 | +- `container logs ggo-apple-test` |
| 183 | +- `container stop ggo-apple-test` |
| 184 | +Expected: commands succeed |
| 185 | + |
| 186 | +**Step 4: Record results** |
| 187 | +Note any failures or deviations. |
| 188 | + |
| 189 | +--- |
| 190 | + |
| 191 | +**Global verification after each Go change:** |
| 192 | +- Run: `golangci-lint run --fix` |
| 193 | +- Run: `go test ./...` (at least once before finalization) |
| 194 | + |
0 commit comments