From f9d85e16efefadf5a26b9574a218da2d464ea272 Mon Sep 17 00:00:00 2001 From: Christophe Vila Date: Fri, 24 Jul 2026 21:08:07 +0200 Subject: [PATCH] Rename smartrun to geniusrun throughout the codebase Updates the Go module path, cmd/smartrund -> cmd/geniusrund, the smartrun-dev skill, .gitignore, and every reference in docs/CLAUDE.md to match. --- .../{smartrun-dev => geniusrun-dev}/SKILL.md | 16 ++--- .gitignore | 2 +- CLAUDE.md | 12 ++-- backend/cmd/{smartrund => geniusrund}/main.go | 14 ++--- backend/cmd/seedsample/main.go | 10 ++-- backend/go.mod | 2 +- backend/internal/api/activities.go | 2 +- backend/internal/api/api_test.go | 8 +-- backend/internal/api/auth.go | 2 +- backend/internal/api/display_fields.go | 2 +- backend/internal/api/kinds.go | 4 +- backend/internal/api/profile.go | 2 +- backend/internal/api/progression.go | 2 +- backend/internal/api/reclassify.go | 2 +- backend/internal/api/review.go | 2 +- backend/internal/api/server.go | 8 +-- backend/internal/classify/rule.go | 2 +- backend/internal/config/config.go | 12 ++-- backend/internal/garmin/client.go | 6 +- backend/internal/garmin/mock/mock.go | 2 +- backend/internal/store/activities.go | 2 +- backend/internal/store/db.go | 4 +- .../migrations/0009_backfill_horizon.sql | 2 +- backend/internal/store/store_test.go | 4 +- backend/internal/sync/mapping.go | 6 +- backend/internal/sync/service.go | 6 +- backend/internal/sync/service_test.go | 10 ++-- backend/start.sh | 4 +- .../plans/2026-07-17-profile-and-taxonomy.md | 60 +++++++++---------- ...profile-taxonomy-analysis-engine-design.md | 6 +- 30 files changed, 108 insertions(+), 108 deletions(-) rename .claude/skills/{smartrun-dev => geniusrun-dev}/SKILL.md (88%) rename backend/cmd/{smartrund => geniusrund}/main.go (87%) diff --git a/.claude/skills/smartrun-dev/SKILL.md b/.claude/skills/geniusrun-dev/SKILL.md similarity index 88% rename from .claude/skills/smartrun-dev/SKILL.md rename to .claude/skills/geniusrun-dev/SKILL.md index 81edcc0..0c5a34e 100644 --- a/.claude/skills/smartrun-dev/SKILL.md +++ b/.claude/skills/geniusrun-dev/SKILL.md @@ -1,13 +1,13 @@ --- -name: smartrun-dev -description: Use when working on the smartrun repo (backend Go services, classification rule engine, mcp-garmin integration, or frontend) to stay consistent with established conventions. +name: geniusrun-dev +description: Use when working on the geniusrun repo (backend Go services, classification rule engine, mcp-garmin integration, or frontend) to stay consistent with established conventions. --- -# smartrun-dev +# geniusrun-dev ## Project overview -smartrun is a personal web app that pulls running activities from Garmin Connect (via the `mcp-garmin` MCP server), classifies each run into a user-defined "workout kind" (Easy, Tempo, Threshold, Interval, ...), and charts progression over time per kind. Ambiguous runs (matching zero, multiple, or only weakly one kind) go to a manual review queue instead of being silently misclassified. +geniusrun is a personal web app that pulls running activities from Garmin Connect (via the `mcp-garmin` MCP server), classifies each run into a user-defined "workout kind" (Easy, Tempo, Threshold, Interval, ...), and charts progression over time per kind. Ambiguous runs (matching zero, multiple, or only weakly one kind) go to a manual review queue instead of being silently misclassified. **MVP scope boundary**: sync + classification + review queue + progression charts. A future training-recommendation engine (analyzing aerobic/anaerobic training-effect balance across kinds to suggest what to train next) is explicitly deferred — the schema stores the metrics it would need, but nothing consumes them yet. @@ -15,7 +15,7 @@ smartrun is a personal web app that pulls running activities from Garmin Connect ``` backend/ - cmd/smartrund/ main server entrypoint + cmd/geniusrund/ main server entrypoint cmd/seedsample/ inserts synthetic data for frontend dev/demoing without live Garmin creds cmd/mcpspike/ throwaway MCP-client spike, safe to delete internal/garmin/ MCP client wrapper (auth, get_activities, get_activity_splits, get_activity_details) @@ -75,9 +75,9 @@ Each `workout_kinds.rule_json` is a recursive AND/OR condition tree (`internal/c ## Dev workflow -- Backend: `cd backend && go run ./cmd/smartrund` (needs `MCP_GARMIN_PYTHON`/`MCP_GARMIN_SERVER` env vars for the mcp-garmin subprocess paths; see `internal/config/config.go` for all knobs). Garmin credentials are **not** env vars — they live in the `profile` singleton row (`internal/store.Profile`), set via the frontend's Profile page or directly through `PUT /api/profile`. +- Backend: `cd backend && go run ./cmd/geniusrund` (needs `MCP_GARMIN_PYTHON`/`MCP_GARMIN_SERVER` env vars for the mcp-garmin subprocess paths; see `internal/config/config.go` for all knobs). Garmin credentials are **not** env vars — they live in the `profile` singleton row (`internal/store.Profile`), set via the frontend's Profile page or directly through `PUT /api/profile`. - Frontend: `cd frontend && npm run dev` (set `VITE_API_BASE_URL` if the backend isn't on `localhost:8080`). -- No live Garmin account needed for frontend/UI work: `go run ./cmd/seedsample -db /tmp/sample.db` seeds realistic activities/laps/kinds and runs them through the real classification engine, then point `smartrund` at that DB. +- No live Garmin account needed for frontend/UI work: `go run ./cmd/seedsample -db /tmp/sample.db` seeds realistic activities/laps/kinds and runs them through the real classification engine, then point `geniusrund` at that DB. - `internal/garmin/mock` provides a fake `Client` for tests that need to exercise `internal/sync`/`internal/api` without a live subprocess. - Migrations: add a new numbered file under `internal/store/migrations/`, never edit an already-applied one (the runner tracks applied filenames in a `schema_migrations` table). @@ -87,4 +87,4 @@ Each `workout_kinds.rule_json` is a recursive AND/OR condition tree (`internal/c - `internal/classify` tests are pure (no DB/network): construct a `MetricContext` + `RuleKind`s directly. - `internal/store` and `internal/sync` tests open a real temp-file SQLite DB (`store.Open` against `t.TempDir()`) — this is deliberate, not mocked, since the migration/SQL correctness is exactly what needs catching. - `internal/api` tests use `httptest` against a `Server` wired to a temp DB + `mock.Client`. -- The MCP/Garmin integration itself can't be safely automated (real account, MFA, rate limits) — it's a manual smoke test via `cmd/mcpspike` or the real `smartrund` auth endpoints. +- The MCP/Garmin integration itself can't be safely automated (real account, MFA, rate limits) — it's a manual smoke test via `cmd/mcpspike` or the real `geniusrund` auth endpoints. diff --git a/.gitignore b/.gitignore index 4c7a59a..434b8fd 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ # Go -backend/smartrund +backend/geniusrund backend/*.db backend/*.db-journal diff --git a/CLAUDE.md b/CLAUDE.md index 9ac4eeb..f7c40d4 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -4,7 +4,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co ## Project overview -geniusrun (Go module `smartrun/backend`) is a personal web app that pulls running activities from Garmin Connect (via the `mcp-garmin` MCP server), classifies each run into one of a fixed set of running-specific "workout kinds" (Easy, Long, 60' Threshold, 30' Threshold, Tempo, Intervals, MAS Test, Race), and charts progression over time per kind. Ambiguous runs (matching zero, multiple, or only weakly one kind) go to a manual review queue instead of being silently misclassified. +geniusrun (Go module `geniusrun/backend`) is a personal web app that pulls running activities from Garmin Connect (via the `mcp-garmin` MCP server), classifies each run into one of a fixed set of running-specific "workout kinds" (Easy, Long, 60' Threshold, 30' Threshold, Tempo, Intervals, MAS Test, Race), and charts progression over time per kind. Ambiguous runs (matching zero, multiple, or only weakly one kind) go to a manual review queue instead of being silently misclassified. All Garmin credentials and every tunable analysis-engine parameter (HR zones, phase-detection minutes, pace-artifact filtering, chart colors, etc.) live in a single-row `profile` table, edited from the frontend's Profile screen — there is no multi-profile support, and `internal/config`'s env vars are limited to process-level plumbing (listen addr, DB path, mcp-garmin subprocess paths). @@ -13,13 +13,13 @@ The "Training plan" tab (`frontend/src/pages/Plan.tsx`) is an intentional empty ## Commands Backend (from `backend/`): -- Run the server: `./start.sh` (wraps `go run ./cmd/smartrund` with the local mcp-garmin subprocess paths and DB path already set) or `go run ./cmd/smartrund` directly if you export `MCP_GARMIN_PYTHON`/`MCP_GARMIN_SERVER` yourself. +- Run the server: `./start.sh` (wraps `go run ./cmd/geniusrund` with the local mcp-garmin subprocess paths and DB path already set) or `go run ./cmd/geniusrund` directly if you export `MCP_GARMIN_PYTHON`/`MCP_GARMIN_SERVER` yourself. - Build/vet: `go build ./...` && `go vet ./...` - Format: `gofmt -l .` must report nothing before committing. - All tests: `go test ./...` - Single package: `go test ./internal/store/...` - Single test: `go test ./internal/store/... -run TestProfile -v` -- Seed sample data (no live Garmin account needed): `go run ./cmd/seedsample -db /tmp/sample.db`, then point `smartrund` at that DB via `SMARTRUN_DB_PATH`. +- Seed sample data (no live Garmin account needed): `go run ./cmd/seedsample -db /tmp/sample.db`, then point `geniusrund` at that DB via `GENIUSRUN_DB_PATH`. - Migrations live in `internal/store/migrations/`; add a new numbered file, never edit an already-applied one (the runner tracks applied filenames in a `schema_migrations` table). Frontend (from `frontend/`): @@ -32,7 +32,7 @@ Frontend (from `frontend/`): ``` backend/ - cmd/smartrund/ main server entrypoint + cmd/geniusrund/ main server entrypoint cmd/seedsample/ inserts synthetic data for frontend dev/demoing without live Garmin creds cmd/mcpspike/ throwaway MCP-client spike, safe to delete internal/garmin/ MCP client wrapper (auth, get_activities, get_activity_splits, get_activity_details, get_workout_by_id) @@ -68,7 +68,7 @@ Each `workout_kinds.rule_json` is a recursive AND/OR condition tree (`internal/c - Leaf conditions: `{metric, op, value}`. `op` is `==`, `!=`, `>`, `>=`, `<`, `<=`, or `between` (value is a 2-element array). - Supported metrics (see `internal/sync/mapping.go`'s `buildMetricContext`): `avg_pace_sec_per_km`, `avg_hr`, `avg_hr_pct_max`, `max_hr`, `duration_seconds`, `distance_meters`, `elevation_gain_m`, `aerobic_training_effect`, `anaerobic_training_effect`, `vo2max_value`, `is_race`, `lap_interval_pattern` (0/1), `lap_pace_stddev`, `lap_hr_drift_bpm_per_min`, `lap_hr_recovery_bpm_per_min`. - **Scoring**: each leaf gets a margin-based confidence in ~[0,1] (`between` = distance from center; comparisons = logistic squash of margin past the threshold). Branches aggregate via `min` (AND) / `max` (OR) — no ML, fully explainable. -- **`needs_review` triggers** (in `classify.Classify`): zero kinds matched, 2+ kinds matched, or exactly one matched below `min_confidence` (default from `SMARTRUN_MIN_CONFIDENCE`, 0.6). All three populate `Candidates` for the review UI. +- **`needs_review` triggers** (in `classify.Classify`): zero kinds matched, 2+ kinds matched, or exactly one matched below `min_confidence` (default from `GENIUSRUN_MIN_CONFIDENCE`, 0.6). All three populate `Candidates` for the review UI. - **Interval detection** (`classify.DetectIntervalPattern`) trusts Garmin's own per-lap `IntensityType` tagging (ACTIVE vs REST/RECOVERY/WARMUP/COOLDOWN) rather than inferring it from pace variance. - **HR drift/recovery** (`classify.HRDrift`/`HRRecovery`): linear regression of heart rate vs elapsed time within a lap's sample window. Drift = rising HR during an active lap. Recovery = HR decay rate during a rest lap, sign-flipped so positive = good recovery. - Max HR for `avg_hr_pct_max` comes from `profile.max_heart_rate`, not a static config value — nil/unset omits that metric from the context rather than erroring. @@ -109,4 +109,4 @@ Each `workout_kinds.rule_json` is a recursive AND/OR condition tree (`internal/c - `internal/classify` tests are pure (no DB/network): construct a `MetricContext` + `RuleKind`s directly. - `internal/store` and `internal/sync` tests open a real temp-file SQLite DB (`store.Open` against `t.TempDir()`) — deliberate, not mocked, since migration/SQL correctness is exactly what needs catching. - `internal/api` tests use `httptest` against a `Server` wired to a temp DB + `mock.Client`. -- The MCP/Garmin integration itself can't be safely automated (real account, MFA, rate limits) — it's a manual smoke test via `cmd/mcpspike` or the real `smartrund` auth endpoints. +- The MCP/Garmin integration itself can't be safely automated (real account, MFA, rate limits) — it's a manual smoke test via `cmd/mcpspike` or the real `geniusrund` auth endpoints. diff --git a/backend/cmd/smartrund/main.go b/backend/cmd/geniusrund/main.go similarity index 87% rename from backend/cmd/smartrund/main.go rename to backend/cmd/geniusrund/main.go index 1861fe0..44b7ab6 100644 --- a/backend/cmd/smartrund/main.go +++ b/backend/cmd/geniusrund/main.go @@ -1,4 +1,4 @@ -// Command smartrund is smartrun's backend server: syncs runs from Garmin, +// Command geniusrund is geniusrun's backend server: syncs runs from Garmin, // classifies them into workout kinds, and serves the REST API the frontend // talks to. package main @@ -11,11 +11,11 @@ import ( "syscall" "time" - "smartrun/backend/internal/api" - "smartrun/backend/internal/config" - "smartrun/backend/internal/garmin" - "smartrun/backend/internal/store" - appsync "smartrun/backend/internal/sync" + "geniusrun/backend/internal/api" + "geniusrun/backend/internal/config" + "geniusrun/backend/internal/garmin" + "geniusrun/backend/internal/store" + appsync "geniusrun/backend/internal/sync" ) func main() { @@ -57,7 +57,7 @@ func main() { httpServer := &http.Server{Addr: cfg.Addr, Handler: server.Router()} go func() { - log.Printf("smartrund listening on %s", cfg.Addr) + log.Printf("geniusrund listening on %s", cfg.Addr) if err := httpServer.ListenAndServe(); err != nil && err != http.ErrServerClosed { log.Fatalf("http server: %v", err) } diff --git a/backend/cmd/seedsample/main.go b/backend/cmd/seedsample/main.go index 45440eb..da9d41d 100644 --- a/backend/cmd/seedsample/main.go +++ b/backend/cmd/seedsample/main.go @@ -12,14 +12,14 @@ import ( "log" "time" - "smartrun/backend/internal/classify" - "smartrun/backend/internal/garmin/mock" - "smartrun/backend/internal/store" - appsync "smartrun/backend/internal/sync" + "geniusrun/backend/internal/classify" + "geniusrun/backend/internal/garmin/mock" + "geniusrun/backend/internal/store" + appsync "geniusrun/backend/internal/sync" ) func main() { - dbPath := flag.String("db", "smartrun_sample.db", "path to the SQLite database to seed") + dbPath := flag.String("db", "geniusrun_sample.db", "path to the SQLite database to seed") flag.Parse() ctx := context.Background() diff --git a/backend/go.mod b/backend/go.mod index a53c39f..cdeaa84 100644 --- a/backend/go.mod +++ b/backend/go.mod @@ -1,4 +1,4 @@ -module smartrun/backend +module geniusrun/backend go 1.26.4 diff --git a/backend/internal/api/activities.go b/backend/internal/api/activities.go index d9cce47..1527673 100644 --- a/backend/internal/api/activities.go +++ b/backend/internal/api/activities.go @@ -6,7 +6,7 @@ import ( "github.com/go-chi/chi/v5" - "smartrun/backend/internal/store" + "geniusrun/backend/internal/store" ) // activityListItem is one row of the activities list, enriched with its diff --git a/backend/internal/api/api_test.go b/backend/internal/api/api_test.go index dea5dd7..0e6f400 100644 --- a/backend/internal/api/api_test.go +++ b/backend/internal/api/api_test.go @@ -13,16 +13,16 @@ import ( "testing" "time" - "smartrun/backend/internal/garmin/mock" - "smartrun/backend/internal/store" - appsync "smartrun/backend/internal/sync" + "geniusrun/backend/internal/garmin/mock" + "geniusrun/backend/internal/store" + appsync "geniusrun/backend/internal/sync" ) func newCtx() context.Context { return context.Background() } func newTestServer(t *testing.T) (*Server, *store.DB) { t.Helper() - db, err := store.Open(filepath.Join(t.TempDir(), "smartrun_test.db")) + db, err := store.Open(filepath.Join(t.TempDir(), "geniusrun_test.db")) if err != nil { t.Fatalf("store.Open: %v", err) } diff --git a/backend/internal/api/auth.go b/backend/internal/api/auth.go index 09d7b6d..a6de82c 100644 --- a/backend/internal/api/auth.go +++ b/backend/internal/api/auth.go @@ -4,7 +4,7 @@ import ( "encoding/json" "net/http" - "smartrun/backend/internal/garmin" + "geniusrun/backend/internal/garmin" ) type authResponse struct { diff --git a/backend/internal/api/display_fields.go b/backend/internal/api/display_fields.go index fe5e6c9..f8cef52 100644 --- a/backend/internal/api/display_fields.go +++ b/backend/internal/api/display_fields.go @@ -3,7 +3,7 @@ package api import ( "encoding/json" - "smartrun/backend/internal/store" + "geniusrun/backend/internal/store" ) // activityResponse adds back ActivityName/ActivityType as fields decoded diff --git a/backend/internal/api/kinds.go b/backend/internal/api/kinds.go index 70b9522..11f86ab 100644 --- a/backend/internal/api/kinds.go +++ b/backend/internal/api/kinds.go @@ -8,8 +8,8 @@ import ( "github.com/go-chi/chi/v5" - "smartrun/backend/internal/classify" - "smartrun/backend/internal/store" + "geniusrun/backend/internal/classify" + "geniusrun/backend/internal/store" ) // workoutKindResponse combines a workout kind's rule/metadata with its pace diff --git a/backend/internal/api/profile.go b/backend/internal/api/profile.go index 8caee52..63df4dc 100644 --- a/backend/internal/api/profile.go +++ b/backend/internal/api/profile.go @@ -5,7 +5,7 @@ import ( "errors" "net/http" - "smartrun/backend/internal/store" + "geniusrun/backend/internal/store" ) // validateProfile checks the HR zones are ascending and gap-free once they diff --git a/backend/internal/api/progression.go b/backend/internal/api/progression.go index d98094a..6ca681d 100644 --- a/backend/internal/api/progression.go +++ b/backend/internal/api/progression.go @@ -7,7 +7,7 @@ import ( "github.com/go-chi/chi/v5" - "smartrun/backend/internal/store" + "geniusrun/backend/internal/store" ) type progressionPoint struct { diff --git a/backend/internal/api/reclassify.go b/backend/internal/api/reclassify.go index 8491931..1ece10a 100644 --- a/backend/internal/api/reclassify.go +++ b/backend/internal/api/reclassify.go @@ -3,7 +3,7 @@ package api import ( "net/http" - "smartrun/backend/internal/store" + "geniusrun/backend/internal/store" ) // handleReclassifyAll re-runs the rule engine for every activity except diff --git a/backend/internal/api/review.go b/backend/internal/api/review.go index 46913b2..467a617 100644 --- a/backend/internal/api/review.go +++ b/backend/internal/api/review.go @@ -8,7 +8,7 @@ import ( "github.com/go-chi/chi/v5" - "smartrun/backend/internal/store" + "geniusrun/backend/internal/store" ) // defaultReviewQueuePageSize matches the frontend's initial/incremental diff --git a/backend/internal/api/server.go b/backend/internal/api/server.go index 5977ee9..1d80598 100644 --- a/backend/internal/api/server.go +++ b/backend/internal/api/server.go @@ -1,4 +1,4 @@ -// Package api is smartrun's HTTP layer: REST handlers over internal/store, +// Package api is geniusrun's HTTP layer: REST handlers over internal/store, // internal/garmin, and internal/sync. package api @@ -11,9 +11,9 @@ import ( "github.com/go-chi/chi/v5" - "smartrun/backend/internal/garmin" - "smartrun/backend/internal/store" - appsync "smartrun/backend/internal/sync" + "geniusrun/backend/internal/garmin" + "geniusrun/backend/internal/store" + appsync "geniusrun/backend/internal/sync" ) // Server wires the HTTP handlers to the app's dependencies. diff --git a/backend/internal/classify/rule.go b/backend/internal/classify/rule.go index 509819b..658eb5b 100644 --- a/backend/internal/classify/rule.go +++ b/backend/internal/classify/rule.go @@ -1,4 +1,4 @@ -// Package classify is smartrun's classification rule engine: it evaluates a +// Package classify is geniusrun's classification rule engine: it evaluates a // user-editable AND/OR condition tree against a run's metrics to decide // which "workout kind" (Easy, Tempo, Threshold, ...) it belongs to. Pure // logic only -- no I/O, no database, no Garmin client -- so it's fully diff --git a/backend/internal/config/config.go b/backend/internal/config/config.go index d3a1856..4ad32ab 100644 --- a/backend/internal/config/config.go +++ b/backend/internal/config/config.go @@ -1,4 +1,4 @@ -// Package config loads smartrund's runtime infrastructure configuration +// Package config loads geniusrund's runtime infrastructure configuration // from environment variables (paths and process settings that don't belong // in the user-editable profile). Garmin credentials and every tunable // analysis-engine parameter live in the profile (internal/store.Profile) @@ -12,7 +12,7 @@ import ( "time" ) -// Config holds smartrund's process-level configuration. +// Config holds geniusrund's process-level configuration. type Config struct { // Addr is the HTTP listen address, e.g. ":8080". Addr string @@ -35,13 +35,13 @@ type Config struct { // for anything optional. Returns an error if a required variable is unset. func Load() (Config, error) { cfg := Config{ - Addr: getEnvDefault("SMARTRUN_ADDR", ":8080"), - DBPath: getEnvDefault("SMARTRUN_DB_PATH", "smartrun.db"), + Addr: getEnvDefault("GENIUSRUN_ADDR", ":8080"), + DBPath: getEnvDefault("GENIUSRUN_DB_PATH", "geniusrun.db"), GarminPythonPath: os.Getenv("MCP_GARMIN_PYTHON"), GarminServerPath: os.Getenv("MCP_GARMIN_SERVER"), GarminTokenStore: os.Getenv("GARMIN_TOKENSTORE"), - MinConfidence: getEnvFloat("SMARTRUN_MIN_CONFIDENCE", 0.6), - IncrementalSyncEvery: getEnvDuration("SMARTRUN_INCREMENTAL_SYNC_EVERY", 6*time.Hour), + MinConfidence: getEnvFloat("GENIUSRUN_MIN_CONFIDENCE", 0.6), + IncrementalSyncEvery: getEnvDuration("GENIUSRUN_INCREMENTAL_SYNC_EVERY", 6*time.Hour), } if cfg.GarminPythonPath == "" { diff --git a/backend/internal/garmin/client.go b/backend/internal/garmin/client.go index c5be4f7..66d62b4 100644 --- a/backend/internal/garmin/client.go +++ b/backend/internal/garmin/client.go @@ -1,5 +1,5 @@ // Package garmin wraps the mcp-garmin MCP server as a narrow Go client -// interface, so the rest of smartrun never deals with MCP/JSON-RPC directly. +// interface, so the rest of geniusrun never deals with MCP/JSON-RPC directly. package garmin import ( @@ -15,7 +15,7 @@ import ( "github.com/mark3labs/mcp-go/mcp" ) -// Client is the interface the rest of smartrun depends on. The real +// Client is the interface the rest of geniusrun depends on. The real // implementation drives mcp-garmin over stdio; internal/garmin/mock provides // a fake for tests and frontend-only development. type Client interface { @@ -94,7 +94,7 @@ func (c *mcpClient) ensureStarted(ctx context.Context) error { initReq := mcp.InitializeRequest{} initReq.Params.ProtocolVersion = mcp.LATEST_PROTOCOL_VERSION - initReq.Params.ClientInfo = mcp.Implementation{Name: "smartrund", Version: "0.0.1"} + initReq.Params.ClientInfo = mcp.Implementation{Name: "geniusrund", Version: "0.0.1"} if _, err := inner.Initialize(ctx, initReq); err != nil { inner.Close() return fmt.Errorf("mcp initialize handshake: %w", err) diff --git a/backend/internal/garmin/mock/mock.go b/backend/internal/garmin/mock/mock.go index 9091d78..d1c5e51 100644 --- a/backend/internal/garmin/mock/mock.go +++ b/backend/internal/garmin/mock/mock.go @@ -5,7 +5,7 @@ package mock import ( "context" - "smartrun/backend/internal/garmin" + "geniusrun/backend/internal/garmin" ) // Client is a fake garmin.Client returning data supplied by the test/caller. diff --git a/backend/internal/store/activities.go b/backend/internal/store/activities.go index 655a38b..6c107c5 100644 --- a/backend/internal/store/activities.go +++ b/backend/internal/store/activities.go @@ -6,7 +6,7 @@ import ( "fmt" ) -// Activity is smartrun's persisted view of a Garmin activity. Field mapping +// Activity is geniusrun's persisted view of a Garmin activity. Field mapping // from the Garmin/MCP response happens in internal/sync, not here, so this // package stays independent of internal/garmin. // diff --git a/backend/internal/store/db.go b/backend/internal/store/db.go index f4b0e07..5083b19 100644 --- a/backend/internal/store/db.go +++ b/backend/internal/store/db.go @@ -1,4 +1,4 @@ -// Package store is smartrun's SQLite persistence layer: activities, laps, +// Package store is geniusrun's SQLite persistence layer: activities, laps, // per-second samples, workout kind rule config, and classification history. package store @@ -15,7 +15,7 @@ import ( //go:embed migrations/*.sql var migrationsFS embed.FS -// DB wraps a *sql.DB opened against a smartrun SQLite database file, with +// DB wraps a *sql.DB opened against a geniusrun SQLite database file, with // migrations already applied. type DB struct { *sql.DB diff --git a/backend/internal/store/migrations/0009_backfill_horizon.sql b/backend/internal/store/migrations/0009_backfill_horizon.sql index 86da295..7cda3fa 100644 --- a/backend/internal/store/migrations/0009_backfill_horizon.sql +++ b/backend/internal/store/migrations/0009_backfill_horizon.sql @@ -1,5 +1,5 @@ -- Backfill horizon used to be a startup-time env var --- (SMARTRUN_BACKFILL_HORIZON_DAYS) with no UI at all -- exactly the kind of +-- (GENIUSRUN_BACKFILL_HORIZON_DAYS) with no UI at all -- exactly the kind of -- "tunable analysis-engine parameter" this profile table exists for. -- Default matches the old env var's default (3 years) so existing -- deployments keep their current behavior until the user changes it. diff --git a/backend/internal/store/store_test.go b/backend/internal/store/store_test.go index b1e77fd..db166ca 100644 --- a/backend/internal/store/store_test.go +++ b/backend/internal/store/store_test.go @@ -8,7 +8,7 @@ import ( func openTestDB(t *testing.T) *DB { t.Helper() - path := filepath.Join(t.TempDir(), "smartrun_test.db") + path := filepath.Join(t.TempDir(), "geniusrun_test.db") db, err := Open(path) if err != nil { t.Fatalf("Open: %v", err) @@ -20,7 +20,7 @@ func openTestDB(t *testing.T) *DB { func f(v float64) *float64 { return &v } func TestMigrateIsIdempotent(t *testing.T) { - path := filepath.Join(t.TempDir(), "smartrun_test.db") + path := filepath.Join(t.TempDir(), "geniusrun_test.db") db1, err := Open(path) if err != nil { t.Fatalf("first Open: %v", err) diff --git a/backend/internal/sync/mapping.go b/backend/internal/sync/mapping.go index 3854777..0484ded 100644 --- a/backend/internal/sync/mapping.go +++ b/backend/internal/sync/mapping.go @@ -5,9 +5,9 @@ import ( "strings" "time" - "smartrun/backend/internal/classify" - "smartrun/backend/internal/garmin" - "smartrun/backend/internal/store" + "geniusrun/backend/internal/classify" + "geniusrun/backend/internal/garmin" + "geniusrun/backend/internal/store" ) // isRunningActivityType reports whether a Garmin activityType.typeKey diff --git a/backend/internal/sync/service.go b/backend/internal/sync/service.go index 47eef5c..b921544 100644 --- a/backend/internal/sync/service.go +++ b/backend/internal/sync/service.go @@ -12,9 +12,9 @@ import ( "sync" "time" - "smartrun/backend/internal/classify" - "smartrun/backend/internal/garmin" - "smartrun/backend/internal/store" + "geniusrun/backend/internal/classify" + "geniusrun/backend/internal/garmin" + "geniusrun/backend/internal/store" ) // Config tunes sync behavior. Zero values fall back to sensible defaults in diff --git a/backend/internal/sync/service_test.go b/backend/internal/sync/service_test.go index c6d661b..471cd20 100644 --- a/backend/internal/sync/service_test.go +++ b/backend/internal/sync/service_test.go @@ -6,17 +6,17 @@ import ( "testing" "time" - "smartrun/backend/internal/classify" - "smartrun/backend/internal/garmin" - "smartrun/backend/internal/garmin/mock" - "smartrun/backend/internal/store" + "geniusrun/backend/internal/classify" + "geniusrun/backend/internal/garmin" + "geniusrun/backend/internal/garmin/mock" + "geniusrun/backend/internal/store" ) func f(v float64) *float64 { return &v } func openTestDB(t *testing.T) *store.DB { t.Helper() - db, err := store.Open(filepath.Join(t.TempDir(), "smartrun_test.db")) + db, err := store.Open(filepath.Join(t.TempDir(), "geniusrun_test.db")) if err != nil { t.Fatalf("store.Open: %v", err) } diff --git a/backend/start.sh b/backend/start.sh index d566563..160a272 100755 --- a/backend/start.sh +++ b/backend/start.sh @@ -4,6 +4,6 @@ cd "$(dirname "$0")" export MCP_GARMIN_PYTHON="${MCP_GARMIN_PYTHON:-/Users/cvila/Dev/scm/scm.vilanet.fr/kriss/mcp-garmin/.venv/bin/python3}" export MCP_GARMIN_SERVER="${MCP_GARMIN_SERVER:-/Users/cvila/Dev/scm/scm.vilanet.fr/kriss/mcp-garmin/server.py}" -export SMARTRUN_DB_PATH="${SMARTRUN_DB_PATH:-smartrun.db}" +export GENIUSRUN_DB_PATH="${GENIUSRUN_DB_PATH:-geniusrun.db}" -exec go run ./cmd/smartrund +exec go run ./cmd/geniusrund diff --git a/docs/superpowers/plans/2026-07-17-profile-and-taxonomy.md b/docs/superpowers/plans/2026-07-17-profile-and-taxonomy.md index c1f52ed..a561f72 100644 --- a/docs/superpowers/plans/2026-07-17-profile-and-taxonomy.md +++ b/docs/superpowers/plans/2026-07-17-profile-and-taxonomy.md @@ -774,7 +774,7 @@ import ( "errors" "net/http" - "smartrun/backend/internal/store" + "geniusrun/backend/internal/store" ) func validateProfile(p store.Profile) error { @@ -1007,8 +1007,8 @@ import ( "github.com/go-chi/chi/v5" - "smartrun/backend/internal/classify" - "smartrun/backend/internal/store" + "geniusrun/backend/internal/classify" + "geniusrun/backend/internal/store" ) // workoutKindResponse combines a workout kind's rule/metadata with its pace @@ -1376,7 +1376,7 @@ Expected: all PASS. `TestBuildMetricContext_DerivesExpectedMetrics` is untouched - [ ] **Step 5: Run the full backend test suite** Run: `cd backend && go build ./... && go vet ./... && go test ./...` -Expected: all PASS. If `cmd/smartrund/main.go` fails to build because it still references `MaxHR: cfg.MaxHR` in its `appsync.Config{...}` literal, that's expected — Task 8 fixes `cmd/smartrund` and `internal/config` together; this step is allowed to show that build failure so you can confirm it's exactly (and only) that one line before moving to Task 8. +Expected: all PASS. If `cmd/geniusrund/main.go` fails to build because it still references `MaxHR: cfg.MaxHR` in its `appsync.Config{...}` literal, that's expected — Task 8 fixes `cmd/geniusrund` and `internal/config` together; this step is allowed to show that build failure so you can confirm it's exactly (and only) that one line before moving to Task 8. - [ ] **Step 6: Commit** @@ -1387,22 +1387,22 @@ git commit -m "feat: classification reads max HR from the profile instead of sta --- -### Task 8: `internal/config` and `cmd/smartrund` — credentials come from the profile +### Task 8: `internal/config` and `cmd/geniusrund` — credentials come from the profile **Files:** - Modify: `backend/internal/config/config.go` (full rewrite — small file, shown complete below) -- Modify: `backend/cmd/smartrund/main.go` (lines 21-46) +- Modify: `backend/cmd/geniusrund/main.go` (lines 21-46) **Interfaces:** - Consumes: `db.GetProfile` (Task 1). -- Removes: `config.Config.GarminEmail`, `config.Config.GarminPassword`, `config.Config.MaxHR` and the `GARMIN_EMAIL`/`GARMIN_PASSWORD`/`SMARTRUN_MAX_HR` env vars — `MCP_GARMIN_PYTHON`/`MCP_GARMIN_SERVER` remain required env vars (infra paths, not secrets). +- Removes: `config.Config.GarminEmail`, `config.Config.GarminPassword`, `config.Config.MaxHR` and the `GARMIN_EMAIL`/`GARMIN_PASSWORD`/`GENIUSRUN_MAX_HR` env vars — `MCP_GARMIN_PYTHON`/`MCP_GARMIN_SERVER` remain required env vars (infra paths, not secrets). - [ ] **Step 1: Rewrite `internal/config/config.go`** Replace the full contents of `backend/internal/config/config.go` with: ```go -// Package config loads smartrund's runtime infrastructure configuration +// Package config loads geniusrund's runtime infrastructure configuration // from environment variables (paths and process settings that don't belong // in the user-editable profile). Garmin credentials and every tunable // analysis-engine parameter live in the profile (internal/store.Profile) @@ -1416,7 +1416,7 @@ import ( "time" ) -// Config holds smartrund's process-level configuration. +// Config holds geniusrund's process-level configuration. type Config struct { // Addr is the HTTP listen address, e.g. ":8080". Addr string @@ -1440,14 +1440,14 @@ type Config struct { // for anything optional. Returns an error if a required variable is unset. func Load() (Config, error) { cfg := Config{ - Addr: getEnvDefault("SMARTRUN_ADDR", ":8080"), - DBPath: getEnvDefault("SMARTRUN_DB_PATH", "smartrun.db"), + Addr: getEnvDefault("GENIUSRUN_ADDR", ":8080"), + DBPath: getEnvDefault("GENIUSRUN_DB_PATH", "geniusrun.db"), GarminPythonPath: os.Getenv("MCP_GARMIN_PYTHON"), GarminServerPath: os.Getenv("MCP_GARMIN_SERVER"), GarminTokenStore: os.Getenv("GARMIN_TOKENSTORE"), - MinConfidence: getEnvFloat("SMARTRUN_MIN_CONFIDENCE", 0.6), - BackfillHorizonDays: getEnvInt("SMARTRUN_BACKFILL_HORIZON_DAYS", 3*365), - IncrementalSyncEvery: getEnvDuration("SMARTRUN_INCREMENTAL_SYNC_EVERY", 6*time.Hour), + MinConfidence: getEnvFloat("GENIUSRUN_MIN_CONFIDENCE", 0.6), + BackfillHorizonDays: getEnvInt("GENIUSRUN_BACKFILL_HORIZON_DAYS", 3*365), + IncrementalSyncEvery: getEnvDuration("GENIUSRUN_INCREMENTAL_SYNC_EVERY", 6*time.Hour), } if cfg.GarminPythonPath == "" { @@ -1494,9 +1494,9 @@ func getEnvDuration(key string, def time.Duration) time.Duration { } ``` -- [ ] **Step 2: Update `cmd/smartrund/main.go` to load credentials from the profile** +- [ ] **Step 2: Update `cmd/geniusrund/main.go` to load credentials from the profile** -`backend/cmd/smartrund/main.go` currently reads (lines 21-46): +`backend/cmd/geniusrund/main.go` currently reads (lines 21-46): ```go func main() { @@ -1566,19 +1566,19 @@ func main() { - [ ] **Step 3: Build and run the full backend test suite** -Run: `cd backend && gofmt -w internal/config/config.go cmd/smartrund/main.go && go build ./... && go vet ./... && go test ./...` +Run: `cd backend && gofmt -w internal/config/config.go cmd/geniusrund/main.go && go build ./... && go vet ./... && go test ./...` Expected: everything builds and all tests PASS. - [ ] **Step 4: Manual smoke test — start the server without Garmin env vars** ```bash cd backend -rm -f /tmp/smartrun_task8.db +rm -f /tmp/geniusrun_task8.db MCP_GARMIN_PYTHON=/Users/kriss/Dev/scm/scm.vilanet.fr/kriss/mcp-garmin/.venv/bin/python \ MCP_GARMIN_SERVER=/Users/kriss/Dev/scm/scm.vilanet.fr/kriss/mcp-garmin/server.py \ -SMARTRUN_DB_PATH=/tmp/smartrun_task8.db \ -SMARTRUN_ADDR=:8085 \ -go run ./cmd/smartrund & +GENIUSRUN_DB_PATH=/tmp/geniusrun_task8.db \ +GENIUSRUN_ADDR=:8085 \ +go run ./cmd/geniusrund & sleep 2 curl -s localhost:8085/api/profile curl -s localhost:8085/api/workout-kinds/ | head -c 300 @@ -1590,7 +1590,7 @@ Expected: the server starts (no "GARMIN_EMAIL required" error), `/api/profile` r - [ ] **Step 5: Commit** ```bash -cd backend && git add internal/config/config.go cmd/smartrund/main.go +cd backend && git add internal/config/config.go cmd/geniusrund/main.go git commit -m "feat: source Garmin credentials from the profile instead of env vars" ``` @@ -2307,11 +2307,11 @@ Change to: cd backend gofmt -w cmd/seedsample/main.go go build ./... && go vet ./... -rm -f /tmp/smartrun_verify.db -go run ./cmd/seedsample -db /tmp/smartrun_verify.db +rm -f /tmp/geniusrun_verify.db +go run ./cmd/seedsample -db /tmp/geniusrun_verify.db ``` -Expected: builds clean, prints `Seeded 10 activities (kinds: Easy=, Tempo=) into /tmp/smartrun_verify.db` with no errors (no unique-constraint failure). +Expected: builds clean, prints `Seeded 10 activities (kinds: Easy=, Tempo=) into /tmp/geniusrun_verify.db` with no errors (no unique-constraint failure). - [ ] **Step 5: Run the full backend test suite one more time** @@ -2322,12 +2322,12 @@ Expected: `gofmt -l .` prints nothing; everything else PASSes. ```bash cd backend -go build -o /tmp/smartrund ./cmd/smartrund +go build -o /tmp/geniusrund ./cmd/geniusrund MCP_GARMIN_PYTHON=/Users/kriss/Dev/scm/scm.vilanet.fr/kriss/mcp-garmin/.venv/bin/python \ MCP_GARMIN_SERVER=/Users/kriss/Dev/scm/scm.vilanet.fr/kriss/mcp-garmin/server.py \ -SMARTRUN_DB_PATH=/tmp/smartrun_verify.db \ -SMARTRUN_ADDR=:8080 \ -/tmp/smartrund & +GENIUSRUN_DB_PATH=/tmp/geniusrun_verify.db \ +GENIUSRUN_ADDR=:8080 \ +/tmp/geniusrund & sleep 1 curl -s localhost:8080/api/workout-kinds/ | python3 -m json.tool | head -30 curl -s localhost:8080/api/profile | python3 -m json.tool @@ -2352,7 +2352,7 @@ Open the printed URL and check: ```bash kill %1 2>/dev/null -rm -f /tmp/smartrund /tmp/smartrun_verify.db +rm -f /tmp/geniusrund /tmp/geniusrun_verify.db ``` - [ ] **Step 9: Commit** diff --git a/docs/superpowers/specs/2026-07-17-profile-taxonomy-analysis-engine-design.md b/docs/superpowers/specs/2026-07-17-profile-taxonomy-analysis-engine-design.md index 91614b4..ea27965 100644 --- a/docs/superpowers/specs/2026-07-17-profile-taxonomy-analysis-engine-design.md +++ b/docs/superpowers/specs/2026-07-17-profile-taxonomy-analysis-engine-design.md @@ -5,7 +5,7 @@ ## Context -smartrun currently classifies runs into arbitrary, user-created "workout +geniusrun currently classifies runs into arbitrary, user-created "workout kinds" using absolute pace/HR thresholds, and has no concept of a user profile — Garmin credentials are passed as environment variables at process start, and there is exactly one hardcoded set of engine parameters. @@ -81,8 +81,8 @@ consolidating all tunable engine parameters: `easy_warmup_minutes`, `easy_cooldown_minutes`, `interval_warmup_minutes`, one pair per non-lap-based type. -`internal/config` (env-var loading) shrinks to just `SMARTRUN_ADDR`, -`SMARTRUN_DB_PATH`, and the mcp-garmin subprocess paths — everything +`internal/config` (env-var loading) shrinks to just `GENIUSRUN_ADDR`, +`GENIUSRUN_DB_PATH`, and the mcp-garmin subprocess paths — everything runtime-tunable moves into `profile`. **Validation** (rejected at save time, not silently accepted):