@wattsonclub/wattson-cli 0.1.0 → 0.1.1

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.
@@ -1,460 +1,122 @@
1
- # Workout text format
1
+ # Workout text output (implemented renderer, not input syntax)
2
2
 
3
- This doc proposes a **chat-friendly, mostly lossless text representation** for workouts.
3
+ `packages/domain/src/index.ts` implements:
4
4
 
5
- Goal:
6
- - make workouts easy to review in chat
7
- - give agents a stable text representation for reasoning and diffs
8
- - make structured validation feedback easier to map back to something human-readable
9
- - prepare for future renderer/parser work without pretending it already exists
10
-
11
- Important status note:
12
- - a first renderer now exists in `packages/domain` via `renderWorkoutText(...)`
13
- - this is still **not** a fully implemented parser contract yet
14
- - canonical source of truth is still the structured workout schema in `packages/domain`
15
-
16
- Related docs:
17
- - `workout-building-blocks.md`
18
- - `commands.md`
19
-
20
- ---
21
-
22
- ## 1. Why this exists
23
-
24
- Schema is necessary, but schema alone is not enough for chat interfaces.
25
-
26
- We need a middle representation that is:
27
- - readable by humans
28
- - easy for agents to reason about
29
- - compact enough for chat
30
- - stable enough to reference in validation/revision feedback
31
- - structured enough that it could later become a real render/parse layer
32
-
33
- This doc proposes **three textual views** of the same workout:
34
-
35
- 1. **Expanded canonical text**
36
- - most explicit
37
- - preferred for careful review
38
- - closest to a future reversible representation
39
-
40
- 2. **Compact text summary**
41
- - shorter
42
- - good for chat replies
43
- - still structurally legible
44
-
45
- 3. **Chat-visual preview**
46
- - highly readable preview
47
- - best for quick inspection
48
- - not necessarily fully lossless on its own
49
-
50
- ---
51
-
52
- ## 2. Design principles
53
-
54
- A good workout text format should be:
55
-
56
- ### Deterministic
57
- The same structured workout should render the same way every time.
58
-
59
- ### Block-oriented
60
- Blocks and interval steps should stay visible as blocks/steps.
61
- Do not flatten everything into prose.
62
-
63
- ### Stable enough for references
64
- Validation should be able to say things like:
65
- - `block 2`
66
- - `block 2 > step 1`
67
- - `line 6`
68
-
69
- ### Honest about precision
70
- If a target is explicit, show it explicitly.
71
- If it is open, show that too.
72
-
73
- ### Friendly to diffs
74
- If a workout changes, the text representation should make the change obvious.
75
-
76
- ---
77
-
78
- ## 3. Expanded canonical text format
79
-
80
- This is the best candidate for a future canonical text representation.
81
-
82
- ## 3.1 Header fields
83
- Suggested header fields:
84
- - `Workout:`
85
- - `Objective:`
86
- - `Sport:`
87
- - `Execution:`
88
- - optionally `Constraints:`
89
- - optionally `Rationale:`
90
-
91
- Example:
92
-
93
- ```text
94
- Workout: VO2 20
95
- Objective: vo2max
96
- Sport: cycling_indoor
97
- Execution: erg
98
- Constraints: indoorOnly, maxPctFtp=1.2
99
- Rationale: short hard day with 3 repeat efforts
5
+ ```ts
6
+ renderWorkoutText(workout, { mode: 'canonical' | 'compact' | 'chat' })
7
+ describeWorkoutPath(path)
8
+ renderValidationIssues(validation)
100
9
  ```
101
10
 
102
- Note:
103
- - `maxPctFtp` is fractional
104
- - use `1.2` for `120% FTP`, not `120`
11
+ The default mode is `compact`. **No reverse parser exists.** None of these renderings is lossless, even the one named `canonical`. Structured `WorkoutDefinition` JSON remains canonical for editing, storage, validation and compilation. Do not send text notation to draft/validate/save as if it were a workout payload. See [workout-building-blocks.md](workout-building-blocks.md) for exact fields and a complete draft-edit-validate-save workflow.
105
12
 
106
- ## 3.2 Block lines
107
- Each top-level block gets an ordinal prefix.
13
+ ## Canonical mode
108
14
 
109
- ### Simple block form
110
- ```text
111
- 1. warmup 5:00 @ 55-70% FTP
112
- ```
113
-
114
- Or, when more explicit:
115
- ```text
116
- 1. warmup 5:00 target=ramp_pct_ftp(0.55->0.70)
117
- ```
118
-
119
- ### Interval-set form
120
- ```text
121
- 2. interval_set x3
122
- 2.1 work 2:00 @ 115% FTP
123
- 2.2 recovery 2:00 @ 50% FTP
124
- ```
15
+ This is an implemented review view, not a proposed future format. For the structured example in the building-blocks reference, the exact renderer output is:
125
16
 
126
- ### Note block form
127
17
  ```text
128
- 3. note "Stay relaxed through the shoulders"
129
- ```
130
-
131
- ## 3.3 Optional inline annotations
132
- Add only when present:
133
- - cadence
134
- - HR constraints
135
- - instructions
136
-
137
- Example:
138
- ```text
139
- 1. steady 8:00 @ 88% FTP cadence=90-95 note="Stay seated"
140
- ```
141
-
142
- Or:
143
- ```text
144
- 1. steady 8:00 @ 88% FTP hr<=165 instructions=["Relax shoulders", "No sprint starts"]
145
- ```
146
-
147
- ## 3.4 Canonical-ish example
148
-
149
- ```text
150
- Workout: VO2 20
18
+ Workout: Controlled VO2 20
151
19
  Objective: vo2max
152
20
  Sport: cycling_indoor
153
21
  Execution: erg
154
- Constraints: indoorOnly, maxPctFtp=1.2
155
-
156
- 1. warmup 5:00 target=ramp_pct_ftp(0.55->0.70)
157
- 2. interval_set x3
158
- 2.1 work 2:00 target=pct_ftp(1.15)
159
- 2.2 recovery 2:00 target=pct_ftp(0.50)
160
- 3. cooldown 3:00 target=ramp_pct_ftp(0.55->0.45)
161
- ```
162
-
163
- This format is not implemented yet, but it is the right target shape.
164
-
165
- ---
166
-
167
- ## 4. Compact text summary
168
-
169
- This is the shorter chat-friendly view.
170
-
171
- Example:
172
-
173
- ```text
174
- WU 5m @55->70%
175
- 3x [2m @115% / 2m @50%]
176
- CD 3m @55->45%
177
- ```
178
-
179
- Or, with title/objective:
180
-
181
- ```text
182
- VO2 20 — vo2max
183
- WU 5m @55->70%
184
- 3x [2m @115% / 2m @50%]
185
- CD 3m @55->45%
186
- ```
187
-
188
- This is great for:
189
- - quick chat confirmation
190
- - draft previews
191
- - revision summaries
192
-
193
- It is weaker for:
194
- - exact validation references
195
- - complex cadence/HR/instruction details
196
- - future lossless round-tripping
197
-
198
- So this should usually be considered a **preview**, not the only representation.
199
-
200
- ---
201
-
202
- ## 5. Chat-visual preview
203
-
204
- This is the “looks good in a message thread” version.
205
-
206
- ## 5.1 Linear block preview
207
-
208
- ```text
209
- VO2 20
210
- [WU 5m] [WORK 2m] [REC 2m] [WORK 2m] [REC 2m] [WORK 2m] [REC 2m] [CD 3m]
211
- 55-70% 115% 50% 115% 50% 115% 50% 55-45%
212
- ```
22
+ Constraints: indoorOnly, maxPctFtp=1.2, maxDuration=20:00, maxContinuousWork=2:00
23
+ Rationale: Three controlled two-minute VO2 efforts with equal recovery.
213
24
 
214
- ## 5.2 Table-like list preview
215
-
216
- ```text
217
- VO2 20
218
- 1. Warmup 5:00 55-70%
219
- 2. Work 2:00 115%
220
- 3. Recovery 2:00 50%
221
- 4. Work 2:00 115%
222
- 5. Recovery 2:00 50%
223
- 6. Work 2:00 115%
224
- 7. Recovery 2:00 50%
225
- 8. Cooldown 3:00 55-45%
25
+ 1. warmup 5:00 target=ramp_pct_ftp(0.55->0.7) instructions=["Build smoothly"]
26
+ 2. interval_set x3 cadence=85-105 instructions=["Stay seated"]
27
+ 2.1 work 2:00 target=pct_ftp(1.15) instructions=["Do not sprint the start"]
28
+ 2.2 recovery 2:00 target=pct_ftp(0.5)
29
+ 3. cooldown 3:00 target=ramp_pct_ftp(0.55->0.45) instructions=["Ease down gradually"]
226
30
  ```
227
31
 
228
- This is the best format when the user mainly wants to inspect whether the shape “looks right.”
32
+ Header behavior:
229
33
 
230
- ---
34
+ - Always emits `Workout`, `Objective`, `Sport`, `Execution`, `Rationale` (summary only).
35
+ - `Execution` lists allowed modes, comma-separated; it is **not** a per-step mode declaration. An empty allowed list displays `erg` as a renderer fallback, although semantic validation still disallows non-open steps.
36
+ - `Constraints` emits indoorOnly when true, maxPctFtp, maxDuration, maxContinuousWork, maxHrBpm when present, in that order. It omits `disallowStanding` and `avoidSprintStarts`.
231
37
 
232
- ## 6. Mapping from structure -> text
38
+ Blocks are numbered from 1, including notes. Interval children show `block.step`, once per pattern (not once per repetition). Simple blocks and interval sets show optional title as JSON-quoted `title=...`; interval **step titles are omitted**. Target is absent if not authored. Optional cadence, HR and nonempty instructions follow. Instruction arrays use JSON quoting. Notes render `N. note "quoted text"` and do not execute.
233
39
 
234
- ## 6.1 Simple block mapping
40
+ Climb output example:
235
41
 
236
- ### Example structured block
237
- ```json
238
- {
239
- "id": "block-1",
240
- "type": "steady",
241
- "durationSec": 600,
242
- "target": { "mode": "pct_ftp", "value": 0.88 },
243
- "cadence": { "min": 90, "max": 95 },
244
- "instructions": ["Stay seated"]
245
- }
246
- ```
247
-
248
- ### Expanded canonical render
249
42
  ```text
250
- 1. steady 10:00 target=pct_ftp(0.88) cadence=90-95 instructions=["Stay seated"]
43
+ 2. climb course="Practice hill" distance=2km ascent=100m avg=5% pacing=0.85
251
44
  ```
252
45
 
253
- ### Compact render
254
- ```text
255
- Steady 10m @88% cadence 90-95
256
- ```
46
+ A climb can additionally show title, cadence, HR, instructions. Pacing is a **fraction**, default .85. It does not print a fake timed duration; only chat mode shows the estimated duration. Course segments, geometry, source/attribution and most other course metadata are omitted.
257
47
 
258
- ## 6.2 Interval-set mapping
259
-
260
- ### Example structured block
261
- ```json
262
- {
263
- "id": "block-2",
264
- "type": "interval_set",
265
- "repeat": 3,
266
- "steps": [
267
- {
268
- "id": "step-1",
269
- "type": "work",
270
- "durationSec": 120,
271
- "target": { "mode": "pct_ftp", "value": 1.15 }
272
- },
273
- {
274
- "id": "step-2",
275
- "type": "recovery",
276
- "durationSec": 120,
277
- "target": { "mode": "pct_ftp", "value": 0.50 }
278
- }
279
- ]
280
- }
281
- ```
48
+ ## Compact mode
282
49
 
283
- ### Expanded canonical render
284
- ```text
285
- 2. interval_set x3
286
- 2.1 work 2:00 target=pct_ftp(1.15)
287
- 2.2 recovery 2:00 target=pct_ftp(0.50)
288
- ```
50
+ Exact compact output for the same example:
289
51
 
290
- ### Compact render
291
52
  ```text
292
- 3x [2m @115% / 2m @50%]
53
+ Controlled VO2 20 — vo2max
54
+ WU 5m @55->70% FTP
55
+ 3x [2m @115% FTP / 2m @50% FTP]
56
+ CD 3m @55->45% FTP
293
57
  ```
294
58
 
295
- ---
296
-
297
- ## 7. Human-friendly target rendering rules
298
-
299
- To make chat output readable, we should prefer these human renderings:
300
-
301
- - `pct_ftp(0.55)` -> `55% FTP`
302
- - `ramp_pct_ftp(0.55->0.70)` -> `55-70% FTP`
303
- - `watts(300)` -> `300W`
304
- - `ramp_watts(180->260)` -> `180->260W`
305
- - `zone(z2)` -> `Z2`
306
- - `open` -> `open`
307
- - `resistance_level(35)` -> `resistance 35`
308
- - `grade_percent(4)` -> `grade 4%`
59
+ Simple labels: `WU`, `STEADY`, `REC`, `CD`, `RAMP`, `OPEN` (free_ride), `CAD`, `REST`. Whole-minute durations use `Nm`; other durations use clock notation. Simple blocks can append `(cad 85-105, hr<=165)` hints. Instructions and titles are omitted. Interval output includes only count, step durations and targets—not step types, cadence, HR, titles, parent cues or instructions. Notes render `NOTE text`.
309
60
 
310
- Structured payload rule:
311
- - `%FTP` values are fractional
312
- - use `pct_ftp(0.55)` for `55% FTP`
313
- - use `pct_ftp(1.15)` for `115% FTP`
314
- - never send `pct_ftp(55)` or `pct_ftp(115)`
61
+ Climb compact form is `CLIMB <name> <distance> <ascent>m avg <grade>% @<pacing-percent>% FTP est`; `est` marks the pacing assumption, not an exact ride time. It omits cadence/HR/instructions. Compact previews are useful for conversation, not safety review or preserving metadata.
315
62
 
316
- This matters because:
317
- - humans do not want to read raw JSON
318
- - agents still need a stable rendering convention
63
+ ## Chat mode
319
64
 
320
- ---
321
-
322
- ## 8. Validation feedback and text references
323
-
324
- Current validation issues already include:
325
- - `code`
326
- - `message`
327
- - `path[]`
328
- - `severity`
329
-
330
- That is a good foundation.
331
-
332
- The text format should help translate issue paths into human-facing references like:
333
- - `block 2`
334
- - `block 2 > step 1`
335
- - `line 6`
336
-
337
- ### Good future validation examples
65
+ Chat mode flattens repetitions and omits notes, using sequential executable-step ordinals. Its numbering is **not** canonical block numbering. Example (two spaces separate fields):
338
66
 
339
67
  ```text
340
- error: block 2 > step 1 exceeds maxPctFtp constraint
341
- warning: block 1 cadence cue is underspecified for cadence_drill
342
- error: interval_set x3 causes total duration to exceed requested duration
343
- warning: cooldown is unusually short for threshold objective
68
+ Controlled VO2 20 — vo2max
69
+ 1. Warmup 5:00 55->70% FTP
70
+ 2. Work 2:00 115% FTP cad 85-105
71
+ 3. Recovery 2:00 50% FTP cad 85-105
72
+ 4. Work 2:00 115% FTP cad 85-105
73
+ 5. Recovery 2:00 50% FTP cad 85-105
74
+ 6. Work 2:00 115% FTP cad 85-105
75
+ 7. Recovery 2:00 50% FTP cad 85-105
76
+ 8. Cooldown 3:00 55->45% FTP
344
77
  ```
345
78
 
346
- That would make the validate/revise loop much better in chat.
347
-
348
- ---
349
-
350
- ## 9. Recommended representation strategy
79
+ Cadence/HR reflect merged set/step values. Chat output omits instructions and titles, and uses source type labels (`Cadence Drill`, `Free Ride`, etc.). Climb fields are `Climb`, course name/distance/ascent, `~M:SS` estimated time, `avg grade N%`, then cadence/HR when present. The rider ends a climb by completing its virtual distance, not by waiting for that estimate.
351
80
 
352
- Use all three, with different purposes:
81
+ ## Exact target notation
353
82
 
354
- ### Structured schema
355
- Use for:
356
- - storage
357
- - validation
358
- - revision
359
- - runtime compilation
83
+ | Structured target | Canonical fragment | Compact/chat human target |
84
+ | --- | --- | --- |
85
+ | `{"mode":"open"}` | `target=open` | `open` |
86
+ | `{"mode":"watts","value":200}` | `target=watts(200)` | `200W` |
87
+ | `{"mode":"pct_ftp","value":0.88}` | `target=pct_ftp(0.88)` | `88% FTP` |
88
+ | `{"mode":"zone","value":"z2"}` | `target=zone(z2)` | `Z2` |
89
+ | `{"mode":"ramp_watts","start":100,"end":180}` | `target=ramp_watts(100->180)` | `100->180W` |
90
+ | `{"mode":"ramp_pct_ftp","start":0.55,"end":0.7}` | `target=ramp_pct_ftp(0.55->0.7)` | `55->70% FTP` |
91
+ | `{"mode":"resistance_level","value":35}` | `target=resistance_level(35)` | `resistance 35` |
92
+ | `{"mode":"grade_percent","value":4}` | `target=grade_percent(4)` | `grade 4%` |
360
93
 
361
- ### Expanded canonical text
362
- Use for:
363
- - careful review
364
- - agent reasoning
365
- - diffing
366
- - future parser/renderer contract
94
+ Compact prefixes target text with `@`; chat does not. Missing target prints no fragment; explicit open prints `open`. Values use fractions in JSON, not whole percentages. Formatting rounds general numeric values to at most two decimal places and distances to one decimal km; it is not an exact numeric serialization. Clock durations use M:SS below one hour and H:MM:SS above it.
367
95
 
368
- ### Compact/chat-visual render
369
- Use for:
370
- - chat previews
371
- - quick approval loops
372
- - human readability
96
+ Cadence rendering selects `min-max`, `min+`, `<=max`, then cue text, then literal `cue` for an empty object. When numerical bounds exist, cue text is not shown. HR rendering chooses `hr<=capBpm` when present, otherwise `hrSoft<=softCapBpm`; **it does not display both**, whereas compilation uses the lower of both as a safety bound. Review structured HR before execution.
373
97
 
374
- ---
98
+ ## Validation references
375
99
 
376
- ## 10. Practical examples for chat interfaces
377
-
378
- ## 10.1 User says: "Create me a 20 min session with 3 hard intervals"
379
-
380
- Agent could reply with:
100
+ `describeWorkoutPath(["blocks","1","steps","0","repeat","2","hr"])` returns:
381
101
 
382
102
  ```text
383
- Draft preview — VO2 20
384
- WU 5m @55->70%
385
- 3x [2m @115% / 2m @50%]
386
- CD 3m @55->45%
103
+ block 2 > step 1 > repeat 3 > hr
387
104
  ```
388
105
 
389
- And optionally also keep the canonical-ish version for internal validation/revision.
390
-
391
- ## 10.2 User says: "Make it slightly easier"
392
-
393
- Agent could show a diff-like preview:
106
+ Indices in issue paths are zero-based strings; display ordinals are one-based. Empty path renders `workout`. Non-block paths become dot-separated (`constraints.maxDurationSec`). `renderValidationIssues` returns an array of lines:
394
107
 
395
108
  ```text
396
- Before:
397
- 3x [2m @115% / 2m @50%]
398
-
399
- After:
400
- 3x [2m @110% / 2m @50%]
109
+ - error [block 2 > step 1 > repeat 3] target.intensity.exceeds_max_pct_ftp: Target intensity 130% FTP exceeds maxPctFtp 120%.
401
110
  ```
402
111
 
403
- That makes revision feel concrete.
404
-
405
- ## 10.3 User says: "Too long — shorten it"
406
-
407
- Agent could show:
408
-
409
- ```text
410
- Before:
411
- WU 5m
412
- 3x [2m @115% / 2m @50%]
413
- CD 3m
414
-
415
- After:
416
- WU 4m
417
- 3x [2m @112% / 90s @50%]
418
- CD 2m
419
- ```
420
-
421
- That is much easier to approve in chat than raw JSON.
422
-
423
- ---
424
-
425
- ## 11. Recommended next implementation follow-on
426
-
427
- The next practical code step after this doc should probably be:
428
-
429
- 1. add a renderer helper that turns structured workouts into:
430
- - expanded canonical text
431
- - compact text summary
432
- - chat-visual preview
433
- 2. use that renderer in CLI/docs/tests
434
- 3. later decide whether we want a reverse parser for a canonical text form
435
-
436
- Important sequencing point:
437
- - renderer first
438
- - parser later, only if it proves valuable
439
-
440
- ---
441
-
442
- ## 12. Current implementation status
443
-
444
- Currently implemented:
445
- - `renderWorkoutText(workout, { mode: "compact" | "canonical" | "chat" })` in `packages/domain`
446
- - compact preview is now used in planner CLI workout summaries where a full structured workout is available
447
-
448
- Not implemented yet:
449
- - reverse parser from text -> workout structure
450
- - line-stable validation references in CLI output
451
- - richer diff renderers for workout revisions
112
+ Human-friendly references already exist; line-stable references and a general revision-diff renderer/parser are not implemented. Use actual issue code/message/path, not invented coaching warnings (there is no general “cooldown too short” or “cadence drill underspecified” check).
452
113
 
453
- ## 13. Bottom line
114
+ ## Review workflow
454
115
 
455
- The workout schema should stay canonical.
456
- But for chat interfaces, agents need a stable text form that sits between:
457
- - raw JSON
458
- - vague prose
116
+ 1. Keep structured candidate JSON, including full metadata, constraints and course attribution.
117
+ 2. Render canonical for block review; compact for a short user preview; chat for expanded execution order.
118
+ 3. Validate the structured candidate and inspect computed duration plus runtime plan. Renderer functions do not validate or recompute stale artifacts.
119
+ 4. Make edits in structured JSON or supported revision operations; render again if useful.
120
+ 5. Save the reviewed JSON with validation/version guards. Text previews are never a substitute for the saved definition.
459
121
 
460
- That is the real leverage point.
122
+ Do not round-trip through any text view: all omit IDs, version/actor timestamps, tags, athlete context and important details. There is no implemented plain-text input grammar, no automatic text-to-workout import, and no promise of lossless text diffs.