plugins/claude-code-fitness-hermit/skills/activity-deep-dive/SKILL.md
Per-activity coaching analysis from Strava. Detects interval vs steady-state sessions and road vs trail terrain, branching the metrics accordingly. Computes zone breakdown and recovery estimate; saves a compiled artifact and returns a compact summary. Run after a workout or to retro-analyze a specific activity.
npx skillsauth add gtapps/claude-code-hermit activity-deep-diveInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Produces a standardised per-activity coaching note. Detects interval vs steady-state sessions and branches accordingly: interval sessions get work-interval HR progression and between-bout recovery quality; steady sessions get pace/HR efficiency and cardiac drift. It also classifies terrain (road vs trail): trail sessions swap pace/HR efficiency for VAM and a grade-adjusted-pace estimate, reframe cardiac drift against the altitude profile, and extend the recovery window for descent load. Both get zone breakdown, recovery estimate, and a coaching note. Saves a compiled artifact and returns a compact summary.
/claude-code-fitness-hermit:activity-deep-dive <activity-id>
/claude-code-fitness-hermit:activity-deep-dive latest
mcp__strava__check-strava-connection — abort if disconnected.mcp__strava__get-athlete-zones (needed for zone calculations)."latest": call mcp__strava__get-recent-activities with limit 1, extract the activity ID.3b. Read .claude-code-hermit/state/activity-notes.json. If the file exists and contains an entry for the resolved activity ID, hold rpe and notes in context for steps 6 and 7.
mcp__strava__get-activity-details — name, type, sport_type, date, distance, duration, moving time, avg/max HR, avg pace, total elevation gain (m)mcp__strava__get-activity-laps — lap splitsmcp__strava__get-activity-streams with keys heartrate,velocity_smooth,altitude,cadence (add watts if sport type is Ride — use altitude,cadence as the baseline keys and add watts only when sport type is known to be Ride)4b. Classify session kind — using the lap data and HR stream already fetched:
interval.steady when the alternation pattern is absent or ambiguous (e.g. a tempo run
with gradually rising HR, a progression run, or a steady run with 1 km auto-laps that have
flat HR across all laps).session_kind (interval or steady) and the interval structure —
N work bouts (the high-HR lap group identified by the clustering signal) and their avg
duration in minutes — for steps 5 and 6.4c. Classify terrain — compute elev_gain_per_km = total_elevation_gain_m / distance_km.
Set terrain:
trail if sport_type == TrailRun and elev_gain_per_km >= 10, OR as a fallback if
elev_gain_per_km >= 20 for a running activity (≥ 100 m gain per 5 km — significant gradient
that decouples pace from effort).road otherwise. This includes a flat TrailRun (elev_gain_per_km < 10, e.g. a canal or
gravel path), where pace stays a valid effort proxy and VAM/GAP add no signal — road metrics
are the right lens.
Hold terrain and elev_gain_per_km for steps 5–7.Compute metrics:
Zone breakdown — from HR stream vs athlete zone boundaries. Calculate % of stream datapoints in each zone (Z1–Z5). If HR stream absent: note "HR data unavailable".
Cadence (running sport types only — Run, TrailRun, VirtualRun) — from the cadence stream. Convert to steps-per-minute first: if the median stream value is < 130 it is single-leg RPM — multiply every value by 2; otherwise use as-is. Compute avg, standard deviation σ, and coefficient of variation CV = σ/μ × 100. Flag if avg < 170 spm (over-striding risk) and/or CV > 8% (high within-run variability — neuromuscular fatigue signal). On trail terrain, report avg and CV for reference but suppress both ⚠ flags — the < 170 spm and CV > 8% thresholds are road-calibrated, and naturally lower, more variable cadence is expected on technical terrain (climbs, descents, footing) rather than a fault. If the cadence stream is absent or empty, or the sport type is not a running type, skip the cadence line entirely (mirror the "HR data unavailable" guard — do not emit a placeholder).
Pace/HR efficiency (steady sessions only, road terrain only) — average pace (min/km)
divided by average HR. Lower = more efficient. Call mcp__strava__get-recent-activities with
perPage: 5 now (only needed for this metric): filter for activities of the same sport type,
exclude the current activity ID, take up to the 4 most recent. Compute the same ratio for each.
Report delta vs prior mean. If terrain == trail, skip this metric entirely and emit no
placeholder — gradient decouples pace from aerobic effort on technical terrain.
Cardiac drift (steady sessions only) — on road (terrain == road): compare average HR in
first 20% of HR stream vs last 20%. Flag if difference > 10 bpm at similar pace (± 15 sec/km).
Report: drift: +N bpm. On trail (terrain == trail): elevation changes confound the
first/last HR split, so instead report a qualitative HR/altitude note: describe whether HR
broadly tracked the climb/descent profile (expected) or showed a decoupled trend worth noting.
VAM (trail terrain only) — vertical ascent meters per hour: total_elevation_gain_m / moving_time_h. Report VAM: N m/h. Contextual benchmarks (heuristic): < 500 m/h is hiking
pace, 500–800 is easy trail, 800–1200 is solid trail running, > 1200 is race/VO2 effort.
GAP estimate (trail terrain only) — session-level grade-adjusted pace: converts the run
to an equivalent flat effort using total elevation gain only (not per-point grade). Heuristic:
equiv_flat_km = distance_km + 0.008 × total_elevation_gain_m (100 m of ascent ≈ 0.8 km of
flat effort). GAP = moving_time / equiv_flat_km — use moving time, not elapsed, so the
estimate is comparable to Strava's moving-time-based actual pace. Report
GAP ~M:SS/km (est, vs actual P:SS/km). Label as heuristic in output.
Work-interval HR progression (interval sessions only) — per-lap avg HR across work laps
in sequence (e.g. I1 157 → I4 168 bpm — progressive ✓). Include the activity max HR
(from get-activity-details) to note the peak bout (e.g. "peaked at 174 bpm on I3"). Use
activity max HR as HRmax — do NOT use the Z5 floor from get-athlete-zones as HRmax.
Recovery between intervals (interval sessions only) — using per-lap avg HR for recovery laps: did HR return toward a recovery zone (Z1–Z2) between bouts? Report as a qualitative observation (e.g. "recovered to ~130 bpm between bouts — adequate" or "incomplete recovery: HR only dropped to 155 bpm before next bout").
Recovery estimate (scale 1–5):
elev_gain_per_km to account for
the muscular load of descents and technical footwork not captured by HR zones (heuristic):elev_gain_per_km < 15 → no extensionelev_gain_per_km 15 to < 30 → add 1 day to the window (e.g. 48h → 72h, 72h → 4 days)elev_gain_per_km >= 30 → add 1–2 days (e.g. 72h → 4–5 days, 5–7 days stays at 5–7 days)
Append (+trail vert) to the recovery line when an extension is applied.Coaching note — 2–3 sentences grounded in the numbers. Highlight what was executed well and one concrete thing to monitor or adjust next time. Reference specific metrics (e.g. "cardiac drift of +14 bpm suggests pacing started too hot").
Format output (8–10 lines):
Steady session (road):
Activity: <name> | <date> | <distance>km in <duration>
Session type: Steady
Zones: Z1 N% / Z2 N% / Z3 N% / Z4 N% / Z5 N%
Cadence: N spm avg (CV: X%) [⚠ over-striding | ⚠ high variability] ← omit line when cadence absent or non-running; show ⚠ only on a tripped flag
Pace/HR efficiency: X.XX min·km⁻¹·bpm⁻¹ (vs prior 4: ±X%)
Cardiac drift: +N bpm (flag if > 10 bpm)
Recovery: N/5 — recommended rest: Xh
Subjective: RPE N/10 — <notes> ← include only when RPE data exists from step 3b
Coaching: <2–3 sentences>
Steady session (trail):
Activity: <name> | <date> | <distance>km in <duration> | <elevation>m gain
Session type: Steady · Trail
Zones: Z1 N% / Z2 N% / Z3 N% / Z4 N% / Z5 N%
Cadence: N spm avg (CV: X%) ← reference only on trail (no ⚠ flags); omit line when cadence absent or non-running
Trail: VAM N m/h | GAP ~M:SS/km (est, vs actual P:SS/km)
HR/altitude: <qualitative note on whether HR tracked the climb/descent profile>
Recovery: N/5 — recommended rest: Xh[(+trail vert)]
Subjective: RPE N/10 — <notes> ← include only when RPE data exists from step 3b
Coaching: <2–3 sentences>
Interval session (road):
Activity: <name> | <date> | <distance>km in <duration>
Session type: Interval (N×~Xmin)
Zones: Z1 N% / Z2 N% / Z3 N% / Z4 N% / Z5 N%
Cadence: N spm avg (CV: X%) [⚠ over-striding | ⚠ high variability] ← omit line when cadence absent or non-running; show ⚠ only on a tripped flag
Intervals: I1 NNNbpm → IN NNNbpm — <progressive ✓ / regressive / flat>; peaked at NNNbpm on IN
Between-bout recovery: HR to ~NNNbpm — <adequate / incomplete>
Recovery: N/5 — recommended rest: Xh
Subjective: RPE N/10 — <notes> ← include only when RPE data exists from step 3b
Coaching: <2–3 sentences>
Interval session (trail):
Activity: <name> | <date> | <distance>km in <duration> | <elevation>m gain
Session type: Interval (N×~Xmin) · Trail
Zones: Z1 N% / Z2 N% / Z3 N% / Z4 N% / Z5 N%
Cadence: N spm avg (CV: X%) ← reference only on trail (no ⚠ flags); omit line when cadence absent or non-running
Trail: VAM N m/h | GAP ~M:SS/km (est, vs actual P:SS/km)
Intervals: I1 NNNbpm → IN NNNbpm — <progressive ✓ / regressive / flat>; peaked at NNNbpm on IN
Between-bout recovery: HR to ~NNNbpm — <adequate / incomplete>
Recovery: N/5 — recommended rest: Xh[(+trail vert)]
Subjective: RPE N/10 — <notes> ← include only when RPE data exists from step 3b
Coaching: <2–3 sentences>
Save compiled artifact to .claude-code-hermit/compiled/activity-<id>-<YYYY-MM-DD>.md:
---
title: "Activity Note — <name> <date>"
type: activity-note
created: <ISO 8601>
session: <current session ID from SHELL.md>
source: manual
tags: [activity-analysis]
activity_id: <id>
sport_type: <Run|TrailRun|Ride|WeightTraining|…>
terrain: <road|trail>
session_kind: <interval|steady>
rpe: <int> # include only when RPE data exists from step 3b
subjective_notes: "<string>" # include only when notes exist from step 3b
---
Body: the full output above.
7b. Write signal-only coaching observations to .claude-code-hermit/sessions/SHELL.md Findings.
From the computed metrics and coaching note, derive 0–N observations that carry a coaching signal worth tracking across sessions: a flagged cardiac drift, a zone-distribution anomaly, a recovery estimate that conflicts with subjective RPE, an efficiency regression vs the prior mean, a flagged cadence (low average or high within-run variability), a notable VAM value, or a trail recovery extension. Do NOT write routine confirmations ("session completed normally") unless they represent a pattern break. If nothing clears the signal bar, skip this step.
First Read .claude-code-hermit/sessions/SHELL.md (Edit requires the file in context, and you need its current ## Findings content to dedup). For each qualifying observation, anchor on the HTML comment and append one line:
old_string: "<!-- Anything unexpected found during work. Proposal-worthy items get their own file. -->"
new_string: "<!-- Anything unexpected found during work. Proposal-worthy items get their own file. -->\nCoaching observation [<label>] (activity <id>): <one-line description grounded in a specific metric>"
Skip the append if a line with the same [<label>] (activity <id>) already exists in ## Findings; re-running the deep-dive must not duplicate observations. If the anchor comment is absent (operator edited SHELL.md), append directly under the ## Findings heading instead.
Labels are kebab-case and reused across sessions for consistency. Prefer an existing label over inventing a synonym. Seed vocabulary: cooldown-hr-elevated, vo2max-stimulus-confirmed, cardiac-drift-high, interval-pacing-inconsistent, recovery-insufficient, efficiency-regression, cadence-low, cadence-variability-high, trail-recovery-extended, vam-notable. Add a new kebab-case label only when none fit.
These lines feed reflect's current-session evidence path; the label convention lets reflect recognize recurrence across sessions, and recurring observations graduate to proposals through the normal reflection-judge / proposal-triage gates.
tools
Presence history & tracker-health report — current home/away state, reliability, recent arrival/departure transitions, and activity patterns for person/device_tracker entities. Use when the operator asks about presence history or when a presence-dependent automation (locks, alarm, vacuum, climate) misbehaves.
development
Evening house brief — end-of-day security check, device status, and energy snapshot. Runs as a daily routine at 22:30 or on demand.
tools
Browse and explain the hermit's Home Assistant automations — list by topic, filter by keyword with plain-language YAML explanations, or sort by last-fired. Read-only. Use when the operator asks "what automations do I have / what does this one do / which haven't fired."
tools
On-demand HA-voice brainstorm — reads entity inventory, automation/script listings, and operator intent to surface at most 2 capability-gap ideas, each gated by proposal-triage before becoming a PROP. Invoke when the operator asks "what automations am I missing?", "any coverage gaps?", or "brainstorm improvements". Never runs autonomously.