@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,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.
|