@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.
- package/CHANGELOG.md +11 -0
- package/README.md +118 -0
- package/dist/bin.js +366 -192
- package/package.json +8 -3
- package/skill/SKILL.md +9 -7
- package/skill/references/commands.md +197 -292
- package/skill/references/workout-building-blocks.md +266 -481
- package/skill/references/workout-text-format.md +71 -409
|
@@ -1,460 +1,122 @@
|
|
|
1
|
-
# Workout text
|
|
1
|
+
# Workout text output (implemented renderer, not input syntax)
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
`packages/domain/src/index.ts` implements:
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
107
|
-
Each top-level block gets an ordinal prefix.
|
|
13
|
+
## Canonical mode
|
|
108
14
|
|
|
109
|
-
|
|
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
|
-
|
|
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
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
43
|
+
2. climb course="Practice hill" distance=2km ascent=100m avg=5% pacing=0.85
|
|
251
44
|
```
|
|
252
45
|
|
|
253
|
-
|
|
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
|
-
##
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
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
|
-
|
|
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
|
-
|
|
81
|
+
## Exact target notation
|
|
353
82
|
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
114
|
+
## Review workflow
|
|
454
115
|
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
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
|
-
|
|
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.
|