feat: coaching-context tools (0.3.0) #6

Merged
Chris Farhood merged 10 commits from feat/coaching-context into main 2026-07-20 20:44:31 +00:00
Owner

0.3.0 — coaching context

Gives the AI coach the athlete's real training context (thresholds, structure) and
closes read/write gaps, per the plans attached to each milestone issue. Implements
the full 0.3.0 milestone: 11 new tools across 5 issues.

Closes #1, closes #2, closes #3, closes #4, closes #5.

Tools added

  • Athlete context (#2): get_athlete_profile, get_sport_settings, get_athlete_summary
  • Activity search + analytics (#3): search_activities, get_activity_best_efforts, get_activity_interval_stats
  • Workout library (#4): get_workouts, get_workout
  • Writes (#5): update_wellness_bulk, update_sport_settings (dual-guardrail)
  • Readiness (#1): get_training_readiness

Notable design points

  • Built against the live Intervals.icu OpenAPI spec — real required params (q,
    stream, start/end index, recalcHrZones) and field names, no guessing.
  • update_sport_settings guards a risky threshold write: warning docstring +
    native ctx.elicit() on capable clients + a hard confirm=True refusal fallback
    (returns the old→new diff) for clients without elicitation. First tool to use Context.
  • Shared _wellness_payload helper backs both single and bulk wellness writes (a
    parity test proves they can't drift); bulk rejects the whole batch on any bad date.
  • get_training_readiness is pure-compute (utils/readiness.py, stdlib only),
    uses the cited Plews & Laursen lnRMSSD/SWC method, and withholds the verdict
    when data is too sparse rather than fabricating one.

Also included

  • ci: fix so release notes are extracted from commitizen's ## vX.Y.Z (date)
    changelog headings (was falling back to a stub).

Quality

  • ruff clean, mypy clean, pytest 92.13% coverage (gate 90%); ~90 new tests.
  • All 11 tools verified registered in both server.py and tools/__init__.py.

Intended delivery model: Claude Opus 4.8.

## 0.3.0 — coaching context Gives the AI coach the athlete's real training context (thresholds, structure) and closes read/write gaps, per the plans attached to each milestone issue. Implements the full 0.3.0 milestone: 11 new tools across 5 issues. Closes #1, closes #2, closes #3, closes #4, closes #5. ### Tools added - **Athlete context (#2):** `get_athlete_profile`, `get_sport_settings`, `get_athlete_summary` - **Activity search + analytics (#3):** `search_activities`, `get_activity_best_efforts`, `get_activity_interval_stats` - **Workout library (#4):** `get_workouts`, `get_workout` - **Writes (#5):** `update_wellness_bulk`, `update_sport_settings` (dual-guardrail) - **Readiness (#1):** `get_training_readiness` ### Notable design points - Built against the live Intervals.icu OpenAPI spec — real required params (`q`, `stream`, `start/end index`, `recalcHrZones`) and field names, no guessing. - `update_sport_settings` guards a risky threshold write: warning docstring + native `ctx.elicit()` on capable clients + a hard `confirm=True` refusal fallback (returns the old→new diff) for clients without elicitation. First tool to use `Context`. - Shared `_wellness_payload` helper backs both single and bulk wellness writes (a parity test proves they can't drift); bulk rejects the whole batch on any bad date. - `get_training_readiness` is pure-compute (`utils/readiness.py`, stdlib only), uses the cited Plews & Laursen lnRMSSD/SWC method, and **withholds** the verdict when data is too sparse rather than fabricating one. ### Also included - `ci:` fix so release notes are extracted from commitizen's `## vX.Y.Z (date)` changelog headings (was falling back to a stub). ### Quality - `ruff` clean, `mypy` clean, `pytest` **92.13%** coverage (gate 90%); ~90 new tests. - All 11 tools verified registered in both `server.py` and `tools/__init__.py`. Intended delivery model: Claude Opus 4.8.
Chris Farhood added 6 commits 2026-07-20 20:10:22 +00:00
Closes the biggest coaching-context gap: expose the athlete's identity/physiology
(get_athlete_profile), per-sport FTP/zones/thresholds (get_sport_settings, with an
optional sport filter and the settings id needed for future writes), and a
training-load summary over a range (get_athlete_summary). New tools/athlete.py plus
formatters in utils/formatting.py; registered in server.py and tools/__init__.py.

Implements #2.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NGzHtDvJur9U7ysgRKRUTN
search_activities queries by name/keyword; get_activity_best_efforts returns
peak values over windows for a stream; get_activity_interval_stats computes
aggregate metrics for an arbitrary stream index range (distinct from the
per-interval get_activity_intervals). Spec-required params enforced (q, stream,
start/end index). Formatters added; tools registered in server.py and __init__.

Implements #3.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NGzHtDvJur9U7ysgRKRUTN
get_workouts lists the reusable library (client-side folder/sport filters);
get_workout renders a single workout including its structured workout_doc steps
via a defensive, depth-capped recursive formatter (repeats, ramps, warmup/
cooldown, power/hr/pace targets). New tools/workouts.py; registered.

Implements #4.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NGzHtDvJur9U7ysgRKRUTN
update_wellness_bulk writes many days in one PUT to /wellness-bulk; the
snake_case->camelCase mapping is extracted into a shared _wellness_payload helper
so single and bulk can't drift, and the whole batch is rejected if any date is
invalid (no partial writes).

update_sport_settings changes FTP/LTHR/pace/zones with a dual guardrail: a warning
docstring, a native ctx.elicit() confirmation on capable clients, and a hard
confirm=True fallback that refuses the write (returning the old->new diff) on
clients without elicitation. It read-modify-writes the full record and passes the
spec-required recalcHrZones query param. Widened the HTTP client's data type to
accept the bulk array.

Implements #5.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NGzHtDvJur9U7ysgRKRUTN
New pure-compute utils/readiness.py (stdlib only, fully unit-tested) assesses
readiness from wellness history: HRV via Plews & Laursen 7-day rolling lnRMSSD vs
baseline +/- SWC, resting-HR and sleep trends, and conventional-direction
subjective inputs (soft warnings only). The get_training_readiness tool fetches
the window, normalizes the date-keyed API response, and renders a banded verdict
with the contributing signals. Verdict is withheld (not fabricated) when HRV is
sparse and fewer than two other core signals have data.

Implements #1. Completes the 0.3.0 coaching-context milestone.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NGzHtDvJur9U7ysgRKRUTN
The release-notes awk matched Keep-a-Changelog "## [X.Y.Z]" brackets, but
commitizen writes "## vX.Y.Z (date)", so every release body fell back to the
"Release vX.Y.Z" stub. Match the "## v" heading form instead so the release
carries its actual changelog section.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NGzHtDvJur9U7ysgRKRUTN
Chris Farhood added 4 commits 2026-07-20 20:28:45 +00:00
Address three confirmed review findings:
- Windows were sample-count based, so "7-day" and "last night" claims could be
  built from weeks-old data. All signals now use calendar windows anchored on a
  reference date (the tool passes today); stale metrics report "no recent data"
  and the verdict is withheld instead of presenting old samples as current.
- rhr_signal's `or vals[:-1]` fallback compared the recent week against itself at
  the sample minimum, reading a uniformly-ill week as "ok". The baseline is now
  disjoint by construction and insufficient baselines return nodata.
- The HRV SWC band had no floor, so a near-constant baseline flagged trivial
  fluctuations (50->49) as red "parasympathetic suppression". SWC now floors at
  0.05 ln units (~5% rMSSD, on the order of normal day-to-day variation).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NGzHtDvJur9U7ysgRKRUTN
update_wellness_bulk kept only recognized snake_case keys and discarded the rest
(e.g. API-style camelCase like restingHR) while reporting success — silent data
loss across up to 92 days. Entries with unknown fields now reject the whole
batch with a message naming the bad and valid field names.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NGzHtDvJur9U7ysgRKRUTN
- get_athlete_summary no longer offers a tags filter: the endpoint's tags param
  filters ATHLETES (coach-facing), not activities, so it produced falsely-empty
  or wrongly-unfiltered summaries (confirmed against the OpenAPI description).
- update_sport_settings: an answered elicitation is now authoritative — accept
  without the confirm tick is a refusal that stops WITHOUT emitting the
  confirm=true fallback instructions (an agentic client could use them to bypass
  the refusal), and an explicit confirm param cannot override it. The elicit
  except no longer swallows failures silently (logged), and an empty-body PUT
  echo renders the merged record instead of an empty settings block.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NGzHtDvJur9U7ysgRKRUTN
The release-notes awk start pattern was an unterminated prefix match ("## v0.3.0"
also re-armed on "## v0.3.01"); escape dots and anchor on the trailing space.

test_tool_auth's hand-counted matrix had drifted (update_wellness from 0.2.0 and
all 11 new tools were missing). Add all 12 and replace the count guard with a
comparison against the live mcp tool registry so drift fails loudly.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NGzHtDvJur9U7ysgRKRUTN
Chris Farhood merged commit 0e3e0264d6 into main 2026-07-20 20:44:31 +00:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: farhoodlabs/intervalsicu-mcp#6