@wattsonclub/wattson-cli 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,507 @@
1
+ # Workout building blocks
2
+
3
+ This doc describes the current **atomic workout grammar** in `packages/domain` and how agents should think about composing workouts from it.
4
+
5
+ It is intentionally agent-oriented.
6
+
7
+ Goal:
8
+ - make the workout structure legible without reading raw Zod code
9
+ - expose the atomic building blocks that agents can combine
10
+ - make validation/revision loops more reliable
11
+ - establish a stable conceptual vocabulary for future CLI/MCP/chat interfaces
12
+
13
+ Canonical source of truth remains the domain schema.
14
+ This doc is a human/agent guide, not a replacement for the schema.
15
+
16
+ Source of truth:
17
+ - `packages/domain/src/index.ts`
18
+
19
+ ---
20
+
21
+ ## 1. Mental model
22
+
23
+ A workout is:
24
+ - metadata + rationale + constraints
25
+ - a sequence of **blocks**
26
+ - where blocks are either:
27
+ - simple timed blocks
28
+ - repeated interval sets
29
+ - notes/support markers
30
+
31
+ The useful abstraction for agents is:
32
+
33
+ 1. choose an **objective**
34
+ 2. choose a **macro shape**
35
+ 3. fill that shape with typed blocks
36
+ 4. choose targets/execution modes/cadence/HR constraints
37
+ 5. validate
38
+ 6. revise until clean
39
+
40
+ In other words:
41
+ - agents should compose workouts from typed pieces
42
+ - agents should not treat the workout as an opaque paragraph
43
+
44
+ ---
45
+
46
+ ## 2. Top-level workout fields that matter most
47
+
48
+ These fields drive authoring behavior most strongly:
49
+
50
+ - `title`
51
+ - `objective`
52
+ - `athleteContext.ftpWatts`
53
+ - `blocks[]`
54
+ - `constraints`
55
+ - `rationale`
56
+ - optional `computed`
57
+ - optional `validation`
58
+ - optional `scheduledDate` (`YYYY-MM-DD`): the day the rider plans to ride it. Set with `schedule_saved_workout`; it orders the library and never starts a ride.
59
+
60
+ Important practical point:
61
+ - the **blocks** define the actual workout shape
62
+ - the **constraints** define what is allowed/safe/preferred
63
+ - the **validation/computed** fields define whether the workout is coherent/executable
64
+
65
+ ---
66
+
67
+ ## 3. Atomic building blocks
68
+
69
+ ## 3.1 Simple timed blocks
70
+
71
+ These are the main single-step building blocks.
72
+
73
+ Supported simple block types:
74
+ - `warmup`
75
+ - `steady`
76
+ - `recovery`
77
+ - `cooldown`
78
+ - `ramp`
79
+ - `free_ride`
80
+ - `cadence_drill`
81
+ - `rest`
82
+
83
+ Shape:
84
+ - `id`
85
+ - `type`
86
+ - `durationSec`
87
+ - optional `target`
88
+ - optional `title`
89
+ - optional `instructions[]`
90
+ - optional `cadence`
91
+ - optional `hr`
92
+
93
+ ### When to use them
94
+
95
+ #### `warmup`
96
+ Use for:
97
+ - gradually preparing the rider
98
+ - low-to-moderate progressive openers
99
+ - cadence ramping without heavy load
100
+
101
+ #### `steady`
102
+ Use for:
103
+ - endurance
104
+ - tempo
105
+ - threshold holds
106
+ - any stable target period
107
+
108
+ #### `recovery`
109
+ Use for:
110
+ - low-load valleys between work bouts
111
+ - aerobic reset periods
112
+
113
+ #### `cooldown`
114
+ Use for:
115
+ - winding the session down
116
+ - reducing target and cadence pressure at the end
117
+
118
+ #### `ramp`
119
+ Use for:
120
+ - explicit increasing/decreasing load phases
121
+ - progressive openers or build segments
122
+
123
+ #### `free_ride`
124
+ Use for:
125
+ - rider-controlled sections
126
+ - open movement / transitions
127
+ - less prescriptive sections where target control should be loose
128
+
129
+ #### `cadence_drill`
130
+ Use for:
131
+ - skill emphasis
132
+ - cadence-specific work where the pedaling cue matters as much as the power target
133
+
134
+ #### `rest`
135
+ Use for:
136
+ - explicit rests distinct from active recovery
137
+ - true low/no-load gaps
138
+
139
+ ---
140
+
141
+ ## 3.2 Interval set block
142
+
143
+ Composite block type:
144
+ - `interval_set`
145
+
146
+ Shape:
147
+ - `id`
148
+ - `type: "interval_set"`
149
+ - `repeat`
150
+ - `steps[]`
151
+ - optional `title`
152
+ - optional `instructions[]`
153
+ - optional `cadence`
154
+ - optional `hr`
155
+
156
+ This is the main repeating composition primitive.
157
+
158
+ Think of it as:
159
+ - a small local workout pattern
160
+ - repeated `n` times
161
+
162
+ That makes it the core tool for:
163
+ - VO2 reps
164
+ - threshold reps
165
+ - over/unders (using step structure)
166
+ - cadence skill repeats
167
+ - repeated recovery/work structures
168
+
169
+ ### Allowed interval-set step types
170
+ Inside an `interval_set`, steps can be:
171
+ - `work`
172
+ - `recovery`
173
+ - `steady`
174
+ - `ramp`
175
+
176
+ This is important:
177
+ - the outer block says “repeat this structure”
178
+ - the inner steps say what the repeated structure actually is
179
+
180
+ ### Good uses
181
+
182
+ #### Classic 3x VO2
183
+ - interval_set repeat 3
184
+ - work 2m @ 115% FTP
185
+ - recovery 2m @ 50% FTP
186
+
187
+ #### Threshold ladder
188
+ - interval_set repeat 2
189
+ - steady 6m @ 100% FTP
190
+ - recovery 2m @ 55% FTP
191
+
192
+ #### Cadence focus set
193
+ - interval_set repeat 4
194
+ - steady 1m @ 75% FTP cadence 105+
195
+ - recovery 1m @ 50% FTP
196
+
197
+ ---
198
+
199
+ ## 3.3 Climb block
200
+
201
+ Composite terrain block:
202
+ - `climb`
203
+
204
+ Shape:
205
+ - `id`
206
+ - `type: "climb"`
207
+ - `course` (a `CourseProfile`: name, source, totals, and `segments[]` of `{ distanceM, gradePct }`)
208
+ - optional `pacingPctFtp` (default `0.85`)
209
+ - optional `title`, `instructions[]`, `cadence`, `hr`
210
+
211
+ A climb is a simulated ascent: the trainer runs in gradient simulation mode and follows the course's grade
212
+ at the rider's virtual position, which the app computes from live watts, rider and bike mass, and the
213
+ profile. The block ends when the rider reaches the end of the course, not on a timer. Its compiled
214
+ `durationSec` is a physics estimate at `pacingPctFtp × FTP` and drives previews, TSS and zone time only.
215
+
216
+ Rules:
217
+ - `constraints.allowedExecutionModes` must include `"grade"`.
218
+ - Get `course` from the planner's climb catalog (`lookup_climb`), a Strava segment import
219
+ (`import_strava_segment`), one of the rider's own Strava rides (`import_strava_activity_course`, source
220
+ kind `strava_activity`, optionally cut to a segment effort or distance window), a Strava route
221
+ (`import_strava_route`, source kind `strava_route`), a GPX file or public GPX link (`import_gpx_course`,
222
+ source kind `gpx`), a myCols or climbfinder page (`import_climb_page`, source kind `climb_page`), the
223
+ road between two places (`build_road_course`, source kind `road`, OpenStreetMap
224
+ geometry plus raster elevation) or generated terrain
225
+ (`generate_terrain_course`, source kind `synthetic`: a distance, average grade and shape, deterministic
226
+ for the same inputs). Saved workouts keep a snapshot of the course, so disconnecting Strava does not
227
+ remove the climb.
228
+ - `athleteContext.weightKg` improves the estimate (75 kg when absent). Bike mass is fixed internally at 7.5 kg; legacy profile/workout bike weights are ignored. W/kg uses rider weight only.
229
+ - Trainers without simulation mode ride the climb in ERG at the pacing watts.
230
+
231
+ Validation warns when a course is under 300 m, averages under 1 %, or has grades outside −20..25 % (they
232
+ are clamped for the trainer).
233
+
234
+ Every ride also gets a virtual distance (`actualMetrics.distanceM`, `ascentM`): climbs from the course
235
+ odometer, ERG steps as a flat road at the recorded power, written into the FIT file so Strava counts the
236
+ mileage. After the ride, `summarize_session` reports each climb the rider started (`summary.climbs`): distance and
237
+ ascent actually ridden from the app's virtual odometer, time, average power, VAM and whether the summit was
238
+ reached. The app's ride history shows the same figures with the profile.
239
+
240
+ ## 3.4 Note block
241
+
242
+ Support-only block type:
243
+ - `note`
244
+
245
+ Shape:
246
+ - `id`
247
+ - `type: "note"`
248
+ - `text`
249
+
250
+ Use for:
251
+ - non-executable guidance
252
+ - rider coaching context
253
+ - explicit reminders that should survive revision/rendering
254
+
255
+ Agents should use `note` sparingly.
256
+ If the content changes the actual executable session shape, it should probably be a real workout block, not just a note.
257
+
258
+ ---
259
+
260
+ ## 4. Target grammar
261
+
262
+ Targets are also typed.
263
+ This matters because agents should choose targets intentionally rather than describing them vaguely.
264
+
265
+ Supported target modes:
266
+ - `open`
267
+ - `watts`
268
+ - `pct_ftp`
269
+ - `zone`
270
+ - `ramp_watts`
271
+ - `ramp_pct_ftp`
272
+ - `resistance_level`
273
+ - `grade_percent`
274
+
275
+ ## 4.1 Practical meaning
276
+
277
+ ### `open`
278
+ No strict load target.
279
+ Good for:
280
+ - free ride
281
+ - easy reset
282
+ - technique-focused movement
283
+
284
+ ### `watts`
285
+ Absolute target.
286
+ Good when:
287
+ - athlete FTP/context is already resolved
288
+ - exact prescription matters
289
+
290
+ ### `pct_ftp`
291
+ Relative target.
292
+ Good default for authoring because:
293
+ - portable across riders
294
+ - easier for agents to reason about conceptually
295
+
296
+ Important:
297
+ - `%FTP` values are fractional in structured payloads
298
+ - use `0.55` for `55% FTP`
299
+ - use `1.15` for `115% FTP`
300
+ - never pass whole percentages like `55` or `115`
301
+
302
+ ### `zone`
303
+ Zone-based target.
304
+ Good for:
305
+ - simple endurance/recovery prescriptions
306
+ - more human-facing authoring when exact percentage is unnecessary
307
+
308
+ ### `ramp_watts` / `ramp_pct_ftp`
309
+ Progressive target from start -> end.
310
+ Good for:
311
+ - warmups
312
+ - build phases
313
+ - progressive efforts
314
+
315
+ ### `resistance_level`
316
+ Trainer resistance mode target.
317
+ Use when explicitly designing for resistance execution.
318
+
319
+ ### `grade_percent`
320
+ Grade-mode target.
321
+ Use when explicitly designing for grade/simulation execution.
322
+
323
+ ---
324
+
325
+ ## 5. Additional per-block constraints/cues
326
+
327
+ Blocks and interval steps can also carry:
328
+
329
+ ### cadence
330
+ Shape:
331
+ - `min`
332
+ - `max`
333
+ - `cue`
334
+
335
+ Use for:
336
+ - skill emphasis
337
+ - spin-up drills
338
+ - low-cadence strength cues
339
+
340
+ ### hr
341
+ Shape:
342
+ - `capBpm`
343
+ - `softCapBpm`
344
+
345
+ Use for:
346
+ - safety bounds
347
+ - recovery gating
348
+ - keeping endurance/recovery blocks honest
349
+
350
+ ### instructions[]
351
+ Use for:
352
+ - rider-facing execution guidance
353
+ - short coaching prompts
354
+ - reminders that belong to the block itself
355
+
356
+ Good examples:
357
+ - `Relax shoulders`
358
+ - `Stay seated`
359
+ - `Smooth pedal stroke`
360
+ - `Do not sprint the start`
361
+
362
+ ---
363
+
364
+ ## 6. Workout-level constraints
365
+
366
+ Workout-level constraints currently include:
367
+ - `maxDurationSec`
368
+ - `maxPctFtp`
369
+ - `maxContinuousWorkSec`
370
+ - `maxHrBpm`
371
+ - `allowedExecutionModes`
372
+ - `disallowStanding`
373
+ - `avoidSprintStarts`
374
+ - `indoorOnly`
375
+
376
+ These are important because they tell the agent what *not* to build.
377
+
378
+ Important:
379
+ - `maxPctFtp` is also fractional
380
+ - use `1.2` for `120% FTP`, not `120`
381
+
382
+ Practical rule:
383
+ - if the user request conflicts with workout constraints, the agent should prefer a legal workout + explanation over inventing an invalid one and hoping validation fixes it later
384
+
385
+ ---
386
+
387
+ ## 7. Composition patterns agents should use
388
+
389
+ These are not hardcoded templates.
390
+ They are strong composition patterns.
391
+
392
+ ## 7.1 Endurance ride with gentle structure
393
+ - warmup
394
+ - steady endurance block
395
+ - optional cadence drill inserts
396
+ - cooldown
397
+
398
+ ## 7.2 VO2 session
399
+ - warmup
400
+ - interval_set(work/recovery x n)
401
+ - cooldown
402
+
403
+ ## 7.3 Threshold / sweet spot session
404
+ - warmup
405
+ - longer steady work block(s)
406
+ - recovery between blocks if needed
407
+ - cooldown
408
+
409
+ ## 7.4 Skills session
410
+ - warmup
411
+ - cadence_drill or free_ride sections with explicit cadence cues
412
+ - light steady aerobic work
413
+ - cooldown
414
+
415
+ ## 7.5 Recovery session
416
+ - warmup or very gentle open start
417
+ - low-load steady / open blocks
418
+ - cadence comfort cues
419
+ - cooldown optional if already very easy
420
+
421
+ ---
422
+
423
+ ## 8. What agents should usually do first
424
+
425
+ When authoring from natural language, agents should generally choose in this order:
426
+
427
+ 1. infer the workout objective
428
+ 2. infer intended duration
429
+ 3. choose whether the workout is mostly:
430
+ - steady/simple blocks
431
+ - repeated intervals
432
+ - skills/free ride
433
+ 4. choose target style:
434
+ - `pct_ftp` is usually the safest authoring default
435
+ - `zone` is okay for simpler low-intensity prescriptions
436
+ - `watts` is better when exact absolute load matters
437
+ 5. add cadence / HR cues only when they actually help
438
+ 6. validate before saving
439
+
440
+ ---
441
+
442
+ ## 9. How validation should fit into authoring
443
+
444
+ Current validation structure already includes:
445
+ - `status: pass | warn | fail`
446
+ - `issues[]`
447
+ - each issue has:
448
+ - `code`
449
+ - `message`
450
+ - `path[]`
451
+ - `severity: warning | error`
452
+
453
+ That is already a strong base for agent loops.
454
+
455
+ The practical loop should be:
456
+ 1. draft from blocks
457
+ 2. validate
458
+ 3. inspect issue `severity`
459
+ 4. use issue `path` to identify which block or step is the problem
460
+ 5. revise only the relevant part
461
+ 6. validate again
462
+
463
+ That is much better than “draft once and hope.”
464
+
465
+ ---
466
+
467
+ ## 10. What usually validates/revises well
468
+
469
+ Agents will usually do better when they:
470
+ - use a clear warmup / work / cooldown shape
471
+ - use `pct_ftp` rather than over-specific absolute watts too early
472
+ - keep repeated sets explicit with `interval_set`
473
+ - keep rider coaching in `instructions[]` rather than in long prose
474
+ - use workout constraints honestly
475
+ - treat `validate` as part of authoring, not as a final optional extra
476
+
477
+ ---
478
+
479
+ ## 11. Common mistakes agents should avoid
480
+
481
+ ### 1. Treating notes as executable structure
482
+ If it affects the ride shape, make it a block.
483
+
484
+ ### 2. Overusing vague prose instead of typed blocks
485
+ The more vague the workout description, the harder it is to revise and validate.
486
+
487
+ ### 3. Using live adaptation to solve pre-execution authoring problems
488
+ If the workout is not yet running, revise the saved workout.
489
+ Do not force everything through `session adapt`.
490
+
491
+ ### 4. Inventing unsupported runtime changes
492
+ Current live ops are bounded.
493
+ Structural edits like “add another interval right now” are not first-class runtime ops today.
494
+
495
+ ### 5. Ignoring constraints and expecting validate to rescue everything
496
+ Validation is a loop, not a magic trash compactor.
497
+
498
+ ---
499
+
500
+ ## 12. Recommended next implementation follow-on
501
+
502
+ The next code/doc layers that fit this grammar well are:
503
+ - a canonical text notation for workouts
504
+ - a structured/chat-friendly workout renderer
505
+ - validation feedback that references block/step locations more explicitly in human terms
506
+
507
+ Those should sit on top of this block grammar, not replace it.