refactor: merge internal/sync into internal/garmin, regroup api files and routes

Garmin auth/sync routes move under /api/garmin/*; sync.Service becomes
garmin.Sync with garmin.SyncConfig/ClientConfig; applog becomes
internal/log; the test mock moves into the garmin package as MockClient
(breaking the test-only import cycle the merge created); stale test
URLs and type names updated to match.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-04 16:04:18 +02:00
parent 19c9aecdeb
commit e2b2bf9611
61 changed files with 2007 additions and 982 deletions

View File

@@ -17,25 +17,25 @@
- `GARMIN_WRAPPER_PYTHON` is optional, defaulting to `"python3"` (resolved via `PATH`) — `MCP_GARMIN_PYTHON`/`MCP_GARMIN_SERVER` are retired entirely, not renamed.
- `garminconnect`'s actual method kwargs must be used exactly: `get_activities_by_date(startdate, enddate)`, `get_activity_splits(activity_id)`, `get_activity_details(activity_id)`, `get_workout_by_id(workout_id)` (confirmed via `inspect.signature` against the installed package — note `startdate`/`enddate`, not `start_date`/`end_date`).
- `gofmt -l .` must report nothing; `go build ./...`, `go vet ./...`, and `go test ./...` must all pass before the final commit.
- `pytest` must pass in `backend/internal/garmin/pyscript/`.
- `pytest` must pass in `../../../backend/internal/garmin/wrapper/`.
---
### Task 1: Python wrapper (`wrapper.py`) with its own test suite
**Files:**
- Create: `backend/internal/garmin/pyscript/wrapper.py`
- Create: `backend/internal/garmin/pyscript/pyproject.toml`
- Create: `backend/internal/garmin/pyscript/.gitignore`
- Test: `backend/internal/garmin/pyscript/tests/__init__.py`
- Test: `backend/internal/garmin/pyscript/tests/test_wrapper.py`
- Create: `../../../backend/internal/garmin/wrapper/wrapper.py`
- Create: `../../../backend/internal/garmin/wrapper/pyproject.toml`
- Create: `../../../backend/internal/garmin/wrapper/.gitignore`
- Test: `../../../backend/internal/garmin/wrapper/tests/__init__.py`
- Test: `../../../backend/internal/garmin/wrapper/tests/test_wrapper.py`
**Interfaces:**
- Produces: `wrapper.dispatch(req: dict) -> dict` — the single entry point Go's subprocess talks to indirectly via stdin/stdout; also `wrapper._auth_state`, `wrapper._client`, `wrapper._mfa_input_queue`, `wrapper._login_result_queue`, `wrapper.Garmin` (re-exported name, patchable in tests), used only within this task's own test suite.
- [ ] **Step 1: Create the Python project manifest**
`backend/internal/garmin/pyscript/pyproject.toml`:
`../../../backend/internal/garmin/wrapper/pyproject.toml`:
```toml
[project]
@@ -55,7 +55,7 @@ requires = ["hatchling"]
build-backend = "hatchling.build"
```
`backend/internal/garmin/pyscript/.gitignore`:
`../../../backend/internal/garmin/wrapper/.gitignore`:
```
.venv/
@@ -68,7 +68,7 @@ __pycache__/
Run:
```bash
cd backend/internal/garmin/pyscript
cd backend/internal/garmin/wrapper
python3 -m venv .venv
.venv/bin/pip install -e ".[dev]"
```
@@ -76,9 +76,9 @@ Expected: installs `garminconnect` and `pytest` into `.venv` with no errors.
- [ ] **Step 3: Write the failing test suite**
`backend/internal/garmin/pyscript/tests/__init__.py`: empty file.
`../../../backend/internal/garmin/wrapper/tests/__init__.py`: empty file.
`backend/internal/garmin/pyscript/tests/test_wrapper.py`:
`../../../backend/internal/garmin/wrapper/tests/test_wrapper.py`:
```python
import os
@@ -216,7 +216,7 @@ Expected: `ModuleNotFoundError: No module named 'wrapper'` (or collection failur
- [ ] **Step 5: Write `wrapper.py`**
`backend/internal/garmin/pyscript/wrapper.py`:
`../../../backend/internal/garmin/wrapper/wrapper.py`:
```python
"""Subprocess wrapper around garminconnect, spoken to over newline-delimited
@@ -393,7 +393,7 @@ Expected: all tests PASS.
- [ ] **Step 7: Commit**
```bash
git add backend/internal/garmin/pyscript
git add backend/internal/garmin/wrapper
git commit -m "$(cat <<'EOF'
feat(garmin): add direct garminconnect wrapper script
@@ -584,7 +584,7 @@ Expected: compile failure — `subprocessClient`, `wireResponse`, `roundTrip`, `
```go
// Package garmin wraps a direct garminconnect subprocess (see
// pyscript/wrapper.py) as a narrow Go client interface, so the rest of
// wrapper/wrapper.py) as a narrow Go client interface, so the rest of
// geniusrun never deals with the wire protocol directly.
package garmin
@@ -861,7 +861,7 @@ git add backend/internal/garmin/client.go backend/internal/garmin/client_test.go
git commit -m "$(cat <<'EOF'
feat(garmin): replace mcp-go transport with a JSON-lines subprocess client
subprocessClient spawns the embedded pyscript/wrapper.py over os/exec and
subprocessClient spawns the embedded wrapper/wrapper.py over os/exec and
speaks newline-delimited JSON instead of MCP. Auth/data methods land in
follow-up commits; this is the transport + lifecycle plumbing only.
@@ -1370,8 +1370,8 @@ EOF
### Task 5: Update `internal/config` (drop server-path env var, default the python path)
**Files:**
- Modify: `backend/internal/config/config.go`
- Modify: `backend/internal/config/config_test.go`
- Modify: `../../../backend/internal/config/envconfig.go`
- Modify: `../../../backend/internal/config/envconfig_test.go`
**Interfaces:**
- Consumes: nothing from Tasks 14.
@@ -1379,7 +1379,7 @@ EOF
- [ ] **Step 1: Write the failing tests**
In `backend/internal/config/config_test.go`, update `setRequiredEnv` (remove the two now-optional lines):
In `../../../backend/internal/config/envconfig_test.go`, update `setRequiredEnv` (remove the two now-optional lines):
```go
func setRequiredEnv(t *testing.T) {
@@ -1425,11 +1425,11 @@ func TestLoad_GarminPythonPathExplicitOverridesDefault(t *testing.T) {
- [ ] **Step 2: Run the tests to verify they fail**
Run: `cd backend && go test ./internal/config/... -v`
Expected: `TestLoad_GarminPythonPathDefaultsToPython3` fails (`GarminPythonPath` is currently `""`, and `Load()` currently errors out entirely since `MCP_GARMIN_PYTHON`/`MCP_GARMIN_SERVER` are no longer set by the trimmed `setRequiredEnv`) — every other test in the package should also now fail with "MCP_GARMIN_PYTHON is required", confirming the removed env vars were the only thing missing.
Expected: `TestLoad_GarminPythonPathDefaultsToPython3` fails (`PythonPath` is currently `""`, and `Load()` currently errors out entirely since `MCP_GARMIN_PYTHON`/`MCP_GARMIN_SERVER` are no longer set by the trimmed `setRequiredEnv`) — every other test in the package should also now fail with "MCP_GARMIN_PYTHON is required", confirming the removed env vars were the only thing missing.
- [ ] **Step 3: Update `config.go`**
- [ ] **Step 3: Update `envconfig.go`**
In `backend/internal/config/config.go`, replace the two Garmin subprocess-path fields:
In `../../../backend/internal/config/envconfig.go`, replace the two Garmin subprocess-path fields:
```go
// GarminPythonPath is mcp-garmin's venv python executable.
@@ -1479,7 +1479,7 @@ Expected: all tests PASS.
- [ ] **Step 5: Commit**
```bash
git add backend/internal/config/config.go backend/internal/config/config_test.go
git add backend/internal/config/envconfig.go backend/internal/config/envconfig_test.go
git commit -m "$(cat <<'EOF'
feat(config): drop MCP_GARMIN_SERVER, default GARMIN_WRAPPER_PYTHON
@@ -1528,7 +1528,7 @@ to:
}, appsync.Config{
```
- [ ] **Step 2: Update the `GarminBase` doc comment in `server.go`**
- [ ] **Step 2: Update the `ClientConfig` doc comment in `server.go`**
In `backend/internal/api/server.go`, change:
@@ -1749,4 +1749,4 @@ EOF
## Follow-up note (not a task in this plan)
The standalone `mcp-garmin` repo (`/Users/kriss/Dev/scm/scm.vilanet.fr/kriss/mcp-garmin`) is superseded by `backend/internal/garmin/pyscript/` and can be archived once this plan lands — that's a separate-repo action for you to do directly (e.g. marking it archived on your SCM host), not something this plan's tasks touch.
The standalone `mcp-garmin` repo (`/Users/kriss/Dev/scm/scm.vilanet.fr/kriss/mcp-garmin`) is superseded by `../../../backend/internal/garmin/wrapper/` and can be archived once this plan lands — that's a separate-repo action for you to do directly (e.g. marking it archived on your SCM host), not something this plan's tasks touch.