@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.
- package/CHANGELOG.md +8 -0
- package/README.md +40 -2
- package/dist/THIRD-PARTY-NOTICES.txt +23 -0
- package/dist/bin.js +8302 -0
- package/package.json +32 -4
- package/skill/SKILL.md +60 -0
- package/skill/references/commands.md +375 -0
- package/skill/references/workout-building-blocks.md +507 -0
- package/skill/references/workout-text-format.md +460 -0
|
@@ -0,0 +1,460 @@
|
|
|
1
|
+
# Workout text format
|
|
2
|
+
|
|
3
|
+
This doc proposes a **chat-friendly, mostly lossless text representation** for workouts.
|
|
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
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Note:
|
|
103
|
+
- `maxPctFtp` is fractional
|
|
104
|
+
- use `1.2` for `120% FTP`, not `120`
|
|
105
|
+
|
|
106
|
+
## 3.2 Block lines
|
|
107
|
+
Each top-level block gets an ordinal prefix.
|
|
108
|
+
|
|
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
|
+
```
|
|
125
|
+
|
|
126
|
+
### Note block form
|
|
127
|
+
```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
|
|
151
|
+
Objective: vo2max
|
|
152
|
+
Sport: cycling_indoor
|
|
153
|
+
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
|
+
```
|
|
213
|
+
|
|
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%
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
This is the best format when the user mainly wants to inspect whether the shape “looks right.”
|
|
229
|
+
|
|
230
|
+
---
|
|
231
|
+
|
|
232
|
+
## 6. Mapping from structure -> text
|
|
233
|
+
|
|
234
|
+
## 6.1 Simple block mapping
|
|
235
|
+
|
|
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
|
+
```text
|
|
250
|
+
1. steady 10:00 target=pct_ftp(0.88) cadence=90-95 instructions=["Stay seated"]
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
### Compact render
|
|
254
|
+
```text
|
|
255
|
+
Steady 10m @88% cadence 90-95
|
|
256
|
+
```
|
|
257
|
+
|
|
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
|
+
```
|
|
282
|
+
|
|
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
|
+
```
|
|
289
|
+
|
|
290
|
+
### Compact render
|
|
291
|
+
```text
|
|
292
|
+
3x [2m @115% / 2m @50%]
|
|
293
|
+
```
|
|
294
|
+
|
|
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%`
|
|
309
|
+
|
|
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)`
|
|
315
|
+
|
|
316
|
+
This matters because:
|
|
317
|
+
- humans do not want to read raw JSON
|
|
318
|
+
- agents still need a stable rendering convention
|
|
319
|
+
|
|
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
|
|
338
|
+
|
|
339
|
+
```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
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
That would make the validate/revise loop much better in chat.
|
|
347
|
+
|
|
348
|
+
---
|
|
349
|
+
|
|
350
|
+
## 9. Recommended representation strategy
|
|
351
|
+
|
|
352
|
+
Use all three, with different purposes:
|
|
353
|
+
|
|
354
|
+
### Structured schema
|
|
355
|
+
Use for:
|
|
356
|
+
- storage
|
|
357
|
+
- validation
|
|
358
|
+
- revision
|
|
359
|
+
- runtime compilation
|
|
360
|
+
|
|
361
|
+
### Expanded canonical text
|
|
362
|
+
Use for:
|
|
363
|
+
- careful review
|
|
364
|
+
- agent reasoning
|
|
365
|
+
- diffing
|
|
366
|
+
- future parser/renderer contract
|
|
367
|
+
|
|
368
|
+
### Compact/chat-visual render
|
|
369
|
+
Use for:
|
|
370
|
+
- chat previews
|
|
371
|
+
- quick approval loops
|
|
372
|
+
- human readability
|
|
373
|
+
|
|
374
|
+
---
|
|
375
|
+
|
|
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:
|
|
381
|
+
|
|
382
|
+
```text
|
|
383
|
+
Draft preview — VO2 20
|
|
384
|
+
WU 5m @55->70%
|
|
385
|
+
3x [2m @115% / 2m @50%]
|
|
386
|
+
CD 3m @55->45%
|
|
387
|
+
```
|
|
388
|
+
|
|
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:
|
|
394
|
+
|
|
395
|
+
```text
|
|
396
|
+
Before:
|
|
397
|
+
3x [2m @115% / 2m @50%]
|
|
398
|
+
|
|
399
|
+
After:
|
|
400
|
+
3x [2m @110% / 2m @50%]
|
|
401
|
+
```
|
|
402
|
+
|
|
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
|
|
452
|
+
|
|
453
|
+
## 13. Bottom line
|
|
454
|
+
|
|
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
|
|
459
|
+
|
|
460
|
+
That is the real leverage point.
|