/activity-deep-dive
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
Claude Code
$ npx -y skills add gtapps/hermitd --skill activity-deep-dive --agent claude-code
Codex
$ npx skills add gtapps/hermitd --skill activity-deep-dive -a codex -y
Cursor
$ npx skills add gtapps/hermitd --skill activity-deep-dive -a cursor -y
Gemini CLI
$ npx skills add gtapps/hermitd --skill activity-deep-dive -a gemini-cli -y
GitHub Copilot
$ npx skills add gtapps/hermitd --skill activity-deep-dive -a github-copilot -y
OpenCode
$ npx skills add gtapps/hermitd --skill activity-deep-dive -a opencode -y
Windsurf
$ npx skills add gtapps/hermitd --skill activity-deep-dive -a windsurf -y
SKILL.md
activity-deep-dive.SKILL.mdname: activity-deep-dive
description: 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.
allowed-tools:
- Read
- Write
- Edit
- Bash(bun *fitness-lab.ts*)
Activity Deep-Dive
Produces a standardised per-activity coaching note. All deterministic statistics (session-kind detection, terrain, zones, cadence, efficiency, cardiac drift, VAM/GAP, recovery estimate) are computed by `scripts/fitness-lab.ts` — this skill runs it once, interprets the JSON, and writes the coaching narrative. Raw Strava streams never enter context: the script fetches and reduces them; the skill only sees the reduced metrics.
Interval sessions get work-interval HR progression and between-bout recovery quality; steady sessions get pace/HR efficiency and cardiac drift. 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.
Usage
/hermitd-fitness:activity-deep-dive <activity-id>
/hermitd-fitness:activity-deep-dive latest
Steps
1. **Resolve subjective RPE.** Read `.hermit/state/activity-notes.json`. If it exists and has an entry for the resolved activity ID (or, for `latest`, hold the check until step 2 returns the ID), keep `rpe` and `notes` for the output template and artifact frontmatter. The script does NOT read RPE — this is the skill's job.
2. **Run the analysis script.** Issue a single Bash call:
bun ${CLAUDE_PLUGIN_ROOT}/scripts/fitness-lab.ts analyze <activity-id|latest>The script fetches details, laps, streams, athlete zones, and the recent summary activities (for the efficiency baseline), then emits one JSON object. Handle the error contract:
- `{"error":"strava_auth:<message>"}` (exit 1) → the token is missing or expired. Relay `<message>` verbatim to the operator and stop. Do not compute anything.
- Any other `{"error":"<token>:<detail>"}` (exit 1, e.g. `fetch` or `invalid_activity_id`) → report it and stop.
- Success (exit 0) → parse the JSON and continue.
3. **Interpret the JSON.** The object has these fields (see `thresholds` in the payload for the exact cutoffs behind each classification — cite them in coaching prose without hardcoding a second copy):
- `meta` — `name, date, sport_type, distance_km, moving_time_s, avg_hr, max_hr, total_elevation_gain_m, elev_gain_per_km`.
- `session_kind` — `"interval"` or `"steady"`. `session_detail` carries `cycles`, `differential_bpm`, `work_bouts`, `avg_bout_min` for the `Interval (N×~Xmin)` header, plus `work_segment_hrs` — the ordered per-work-bout average HR (from laps when there were ≥3, else from the HR-stream windows the classifier used). This is the authoritative source for the `I1 → IN` progression, so it renders even on lap-sparse activities.
- `terrain` — `"road"` or `"trail"`.
- `zones` — `[{zone, pct}]` (Z1–Z5), or `null` when HR/zone data is absent (render "HR data unavailable").
- `cadence` — `{avg, sd, cv, flags}` or `null`. `flags` is `["over-striding"]` and/or `["high-variability"]`; it is empty on trail (road-calibrated thresholds are suppressed). Omit the cadence line entirely when `null`.
- `efficiency` — `{current, prior_mean, delta_pct, priors_used}`. Cite on **steady + road** only. `delta_pct` is the signed % vs the prior mean (negative = more efficient). Skip the line when `priors_used` is 0.
- `cardiac_drift_bpm` / `cardiac_drift_flagged` — signed int (rising HR = positive), and whether it cleared the flag threshold. Cite on **steady** sessions only — on an interval session the first-20%/last-20% split straddles work and recovery bouts, so the figure is noise; skip the drift line. `null` on trail (use the `hr_altitude` field instead — see below). `flagged` is already pace-guarded (a negative split won't trip it), so relay it as-is.
- `hr_altitude` — trail only (`null` on road, and `null` on trail when HR/altitude streams are too short or flat to correlate). `{corr, tracks}`: `corr` is the Pearson r of HR vs altitude (rounded); `tracks` is `"tracks"` (r ≥ 0.3 — HR broadly rose on climbs / fell on descents, expected) or `"decoupled"` (HR did not follow the terrain — worth flagging). Render the `HR/altitude:` line from this; when `null`, state the coupling couldn't be assessed rather than inventing one.
- `vam` (m/h) and `gap_per_km` (seconds/km) — trail only; `null` on road. Render GAP as `M:SS/km`.
- `laps` — `[{index, avg_hr, max_hr, distance_km, moving_time_s}]`. For **interval** sessions, build the `I1 → IN` progression from `session_detail.work_segment_hrs` (above), not the raw laps — it already sequences the work bouts whether laps or HR-windows fed the classifier. Use `laps` (and `meta.max_hr` as HRmax, never a zone floor) for the between-bout recovery note and the peak-bout callout; when `laps` is empty, ground those from the progression alone.
- `recovery` — `{band, hours, window, trail_extended}`. `band` is 1–5; `window` is the rendered rest recommendation (already includes any `(+trail vert)` extension).
- `warnings` — degraded-metric notes (short/absent streams). Surface anything material in the coaching note rather than silently dropping it.
4. **Write the 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").
5. Format output (8–10 lines):
**Steady session (road):**
Activity: <name> | <date> | <distance>km in <duration>
Se
Read more
name: activity-deep-dive description: 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. allowed-tools: - Read - Write - Edit - Bash(bun *fitness-lab.ts*)
Activity Deep-Dive
Produces a standardised per-activity coaching note. All deterministic statistics (session-kind detection, terrain, zones, cadence, efficiency, cardiac drift, VAM/GAP, recovery estimate) are computed by `scripts/fitness-lab.ts` — this skill runs it once, interprets the JSON, and writes the coaching narrative. Raw Strava streams never enter context: the script fetches and reduces them; the skill only sees the reduced metrics.
Interval sessions get work-interval HR progression and between-bout recovery quality; steady sessions get pace/HR efficiency and cardiac drift. 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.
Usage
/hermitd-fitness:activity-deep-dive <activity-id> /hermitd-fitness:activity-deep-dive latest
Steps
1. **Resolve subjective RPE.** Read `.hermit/state/activity-notes.json`. If it exists and has an entry for the resolved activity ID (or, for `latest`, hold the check until step 2 returns the ID), keep `rpe` and `notes` for the output template and artifact frontmatter. The script does NOT read RPE — this is the skill's job.
2. **Run the analysis script.** Issue a single Bash call:
bun ${CLAUDE_PLUGIN_ROOT}/scripts/fitness-lab.ts analyze <activity-id|latest>The script fetches details, laps, streams, athlete zones, and the recent summary activities (for the efficiency baseline), then emits one JSON object. Handle the error contract:
- `{"error":"strava_auth:<message>"}` (exit 1) → the token is missing or expired. Relay `<message>` verbatim to the operator and stop. Do not compute anything.
- Any other `{"error":"<token>:<detail>"}` (exit 1, e.g. `fetch` or `invalid_activity_id`) → report it and stop.
- Success (exit 0) → parse the JSON and continue.
3. **Interpret the JSON.** The object has these fields (see `thresholds` in the payload for the exact cutoffs behind each classification — cite them in coaching prose without hardcoding a second copy):
- `meta` — `name, date, sport_type, distance_km, moving_time_s, avg_hr, max_hr, total_elevation_gain_m, elev_gain_per_km`.
- `session_kind` — `"interval"` or `"steady"`. `session_detail` carries `cycles`, `differential_bpm`, `work_bouts`, `avg_bout_min` for the `Interval (N×~Xmin)` header, plus `work_segment_hrs` — the ordered per-work-bout average HR (from laps when there were ≥3, else from the HR-stream windows the classifier used). This is the authoritative source for the `I1 → IN` progression, so it renders even on lap-sparse activities.
- `terrain` — `"road"` or `"trail"`.
- `zones` — `[{zone, pct}]` (Z1–Z5), or `null` when HR/zone data is absent (render "HR data unavailable").
- `cadence` — `{avg, sd, cv, flags}` or `null`. `flags` is `["over-striding"]` and/or `["high-variability"]`; it is empty on trail (road-calibrated thresholds are suppressed). Omit the cadence line entirely when `null`.
- `efficiency` — `{current, prior_mean, delta_pct, priors_used}`. Cite on **steady + road** only. `delta_pct` is the signed % vs the prior mean (negative = more efficient). Skip the line when `priors_used` is 0.
- `cardiac_drift_bpm` / `cardiac_drift_flagged` — signed int (rising HR = positive), and whether it cleared the flag threshold. Cite on **steady** sessions only — on an interval session the first-20%/last-20% split straddles work and recovery bouts, so the figure is noise; skip the drift line. `null` on trail (use the `hr_altitude` field instead — see below). `flagged` is already pace-guarded (a negative split won't trip it), so relay it as-is.
- `hr_altitude` — trail only (`null` on road, and `null` on trail when HR/altitude streams are too short or flat to correlate). `{corr, tracks}`: `corr` is the Pearson r of HR vs altitude (rounded); `tracks` is `"tracks"` (r ≥ 0.3 — HR broadly rose on climbs / fell on descents, expected) or `"decoupled"` (HR did not follow the terrain — worth flagging). Render the `HR/altitude:` line from this; when `null`, state the coupling couldn't be assessed rather than inventing one.
- `vam` (m/h) and `gap_per_km` (seconds/km) — trail only; `null` on road. Render GAP as `M:SS/km`.
- `laps` — `[{index, avg_hr, max_hr, distance_km, moving_time_s}]`. For **interval** sessions, build the `I1 → IN` progression from `session_detail.work_segment_hrs` (above), not the raw laps — it already sequences the work bouts whether laps or HR-windows fed the classifier. Use `laps` (and `meta.max_hr` as HRmax, never a zone floor) for the between-bout recovery note and the peak-bout callout; when `laps` is empty, ground those from the progression alone.
- `recovery` — `{band, hours, window, trail_extended}`. `band` is 1–5; `window` is the rendered rest recommendation (already includes any `(+trail vert)` extension).
- `warnings` — degraded-metric notes (short/absent streams). Surface anything material in the coaching note rather than silently dropping it.
4. **Write the 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").
5. Format output (8–10 lines):
**Steady session (road):**
Activity: <name> | <date> | <distance>km in <duration> Se
Run an always-on Claude Code agent on your own machine, for you or your team. Start and orchestrate sessions from chat in any project it can reach. Take over the same session in the Claude app or the CLI.
Other skills on hermitd.
More skills from other plugins.
browser-use
Direct browser control via CDP for web interaction: automation, scraping, testing, screenshots, and site/app work. Also covers…
agent-browser
Browser automation CLI for AI agents. Use when the user needs to interact with websites, including navigating pages, filling…
Questions about activity-deep-dive.
- Which coding agents can use activity-deep-dive?
- activity-deep-dive installs in Claude Code, Codex, Cursor, Gemini CLI, GitHub Copilot, OpenCode and Windsurf with the skills CLI, using each agent's flag shown on this page. Flowy's auto-invocation, which fires a skill when your prompt matches, runs in Claude Code only.

