A reliability and coaching-correctness release. The ACWR injury gate now runs
on the validated rolling 7d:28d model instead of the EWMA approximation, and a
class of date/anomaly bugs that made the coach misread what happened when is
fixed at the root (with the live failure that surfaced them captured as a
regression test). No new features and no breaking changes for MCP clients —
the tool surface, names, and config are identical to 1.0.0.
Fixed
- Morning-audit crash loop:
scripts/daily_loop.pyno longer dies at
import whendaily_loop.logis held by another writer. The Task Scheduler
action's cmd>>redirect into the same file the script's own
logging.FileHandleropens held the log without write sharing (Windows),
so every scheduled run since the task's 2026-06-10 registration crashed
withPermissionErrorbefore doing any work. The file handler is now
best-effort (console-only fallback) and the shipped task guidance redirects
scheduler output to a separatedaily_loop_task.log. - Planned-vs-actual matcher no longer crosswires types (live coaching
failure, 2026-06-10): pairing now happens ONLY between a planned session
and an actual whose types satisfytaxonomy.types_match, with a new
name-hint fallback so plan "mobility" matches Garmin typeotherwhen the
activity name contains mobility/stretch/yoga ("Cape Town Mobility"); when
several actuals match by type the closest duration wins. Unmatched planned
sessions aremissing, unmatched actuals areunplanned(load included)
— never paired just because both were left over. The skipped-padel day now
reads mobility=completed, padel=MISSING, hard ride=UNPLANNED instead of
two fabricated type_mismatches.type_mismatchsurvives only as a narrow
substitute signal: one unmatched planned session whose type the taxonomy
can't classify (e.g.race) plus exactly one leftover actual. - Today is pending, never missed: planned sessions dated today no longer
producemissinganomalies or land inplan_adherence.skipped_dates— a
06:27 snapshot was flagging the day's sessions as already skipped. The
comparison cutoff is now date < today for missing; today belongs in
pending_dates. Persistent-anomaly registration refuses missing-anomalies
dated today, and cleanup drops previously mis-registered open
missing-anomalies for the current date (they re-register tomorrow if the
session genuinely never happened). Anomaly ids keep the
<date>:<type>:<slug>format. - Snapshot payload is temporally self-anchoring (the coach combined
week-old anomalies into "padel was today"):week_gridentries and all
anomalies carrydays_ago(0 = today), the snapshot carries
week_grid_today,planned_vs_actualcarriesas_of, and every anomaly
summary embeds its absolute date plus a relative phrase
("2026-06-10 (yesterday): ..."), recomputed against the threaded today on
every snapshot.SERVER_INSTRUCTIONSnow mandates trusting
current_time_contextover any date impression from earlier conversation
and stating the current date/day at session start; the full date-discipline
doctrine lives incoach://coaching/doctrine.
Changed
- ACWR cutover to the classic rolling 7d:28d model (owner-approved after
the 90-day shadow period — mean abs diff 0.264, 42% zone mismatch, and the
May 11-13 post-stage-race danger window the EWMA model missed). The rolling
coupled-window ratio (Hulin/Gabbett — the model the 0.8/1.3/1.5 thresholds
were derived against) is now the PRIMARYacwr/acwr_statuseverywhere:
snapshotfitness_metrics,load_hierarchy,acwr_warnings, the weekly
prescription volume adjustment, the coaching score health component, and
sport-specific ACWR (the load hierarchy never mixes models). The legacy
EWMA ratio is demoted tofitness_metrics.acwr_ewma
{value, zone, safe, note}— reference only. The shadow keys
(acwr_rolling,acwr_rolling_status,acwr_shadow) are removed.
CTL/ATL/TSB math, the 0.8/1.3/1.5 thresholds, and the [10, 15, 25] volume
steps are unchanged. - Docs clarify that Cloudflare handling is a library dependency, not custom
code in this repo — Garmin's SSO sits behind Cloudflare and the
garminconnectlibrary handles it natively via itscurl_cffidependency;
this project's old garth + Playwright login fallback was deleted during the
1.0 rebuild. - Docs aligned with MCP spec 2026-07-28 (the stateless-core release):
COACH_TRANSPORT=sseis documented as legacy-only (the HTTP+SSE transport
was formally deprecated upstream), and the deferred elicitation re-add is
redesigned around the MRTR pattern instead of the removed server-initiated
ctx.elicit()flow. No behavior changes — this stdio server needs no
migration.
Added
scripts/recompute_acwr_history.py: supervised one-off migration that
recomputes theacwrfield of every storedfitness_history.jsonsnapshot
(total + per-sport) under the rolling model —--checkdry-run by default,
--applywrites after a one-time.pre-acwr-cutover.bakbackup;
ctl/atl/tsb are never touched.