All notable changes to :wpl_ai.
The format follows Keep a Changelog. This project adheres to Semantic Versioning.
[Unreleased]
[1.6.6] — 2026-05-05
Fixed — 7 silent-failure parser/lexer bugs (TS parity with @gymbile/wpl-ai@1.10.4)
- Bug 1 — digit-leading TAGS value (
531):TAGS 531, strengthnow producestags: ["531", "strength"]instead oftags: [];parse_tag_listaccepts number tokens. - Bug 2 — digit-leading identifier in TAGS list (
1rm_estimate):TAGS strength_test, 1rm_estimate, powerliftingno longer truncates after the digit-leading item; thenumber + bare_wordtoken sequence is glued into a single tag string. - Bug 3 — colon-qualified contraindication name (
acsm:cardiac_rehab_phase_2): the parser now acceptsprefix:suffixform in the contraindication-name slot; glues the colon token into a single qualified identifier string. - Bug 4 — unknown REQUIRES directive silently terminates block: any unrecognised keyword or bare_word inside a REQUIRES block now produces a
ParseErrorwith typeinvalid_structure: "Unknown REQUIRES directive: '...'. Recognized: contraindication, fitness, equipment, age, time_commitment." - Bug 5 —
trigger completion(no-arg) swallows subsequent sections:parse_triggernow emits aParseErrorwith typeinvalid_structure: "Unsupported checkpoint trigger 'completion' — use 'at N weeks' or 'at N days'." - Bug 6 — unknown phase type silently drops the phase:
parse_phasedetects non-recognized type keywords and emits aParseErrorwith typeinvalid_structure: "Unknown phase type '<x>'. Allowed: accumulation, intensification, realization, deload, base, build, peak, recovery, transition." - Bug 7 —
jogging 10min cooldown produces malformed recovery_exercise + phantommorphan: the cooldown block parser now routes<bare_word> <number> <time_unit> EOLto an inlineCardioActivitywithmodality,cardio_type: :continuous, andtotal_durationpopulated correctly.
Added
- Invalid-parser conformance runner:
conformance_test.exsnow also iterateswpl/conformance/invalid/parser/fixtures, verifying thatWplAi.Parser.parse/1returns{:error, ...}with errors matching the expectedtypeandmessagefields.
[1.6.5] — 2026-05-05
Fixed
- compiler: emit
progress.points_systeminstead ofprogress.points— matches schema field name. Previous"points"key produced SCHEMA_VIOLATION when validated. - compiler: day-scoped activity ID counter — auto-IDs were previously per-block; same activity kind in two blocks of the same day collided (DUPLICATE_ID). Now monotonic across all blocks in a day.
[1.6.4] — 2026-05-04
Added
WplAi.Validatormodule — new semantic validator that walks the AST and emits vocabulary warnings (not errors). Mirrors the TypeScriptvalidateSemanticsstep inwpl-ai.WplAi.validate_semantics/1— public API entry point delegating toWplAi.Validator.validate_semantics/1.
Fixed
- validator: refresh MeasurementMetric + Questionnaire vocabulary to schema 1.6.0:
WplAi.Validatorknows the canonical 24-valueMeasurementMetricenum and the 8-valueQuestionnaireenum; legacy string items are checked against the combined (legacy + enum) set; typedMeasurementSpecitems havemetricchecked against the enum set, andquestionnaire(whenmetric == "questionnaire_score") checked against the questionnaire set.
[1.6.3] — 2026-05-04
Changed
- test: per-module unit tests (lexer, parser, vocabularies, exercise_matcher) — 93 new tests.
[1.6.2] — 2026-05-05
Fixed
- Bug 1 — Structured
tempoemit —tempo 3 - 1 - 1 - 0(and3-0-X-1forms) now emits the structured object{eccentric, pause_bottom, concentric, pause_top}instead of a raw string.Xin the concentric position setsexplosive_concentric: true(TS parity). - Bug 2 —
weight N% bwunit field —percentage_bodyweightweight spec now emitsunit: "bw"instead ofunit: "%"(TS parity). - Bug 3 — Calories
kcalunit omitted — when calorie unit is"kcal"(the schema default), theunitfield is now omitted from the compiled output. Onlykcal_per_kgandmultiplier_of_tdeeare emitted (TS parity). - Bug 4 — Nutrition timing
at_timemaps totype: "absolute"—timing at 07:30now emits{type: "absolute", time: "07:30"}(with no seconds, no offset field). Previously emitted{type: "at_time", time: "07:30:00"}(TS parity). - Bug 5 — Nutrition timing
before_workout/after_workoutdrops body content —before_workoutandafter_workoutwere not in the keywords list, causing them to be tokenized as bare words and break the timing parser. Adding them to the lexer keyword list restores full body parsing after a timing directive (TS parity). - Bug 6 — Habit
prescriptionnesting — habit activities now nesttarget,frequency, andreminder_timesunder aprescriptionkey instead of emitting them flat on the activity. Also fixed a parser bug where parsingfrequencyin a habit body calledparse_plan_habit_bodyinstead ofparse_habit_body, silently droppingreminders(TS parity). - Bug 7 —
bodyweightkeyword as exercise modifier — barebodyweightafter reps/sets (e.g.,pull_up 3x8 bodyweight) now attaches asweight: {type: "bodyweight"}on the exercise prescription instead of generating a phantomSimpleActivity(TS parity).
[1.6.1] — 2026-05-04
Fixed
metadata.languagedefault — compiler now always emitsmetadata.language: "en"when the DSL does not specify a language, matching TS compiler behaviour (TS parity).- Auto-derived activity display
name— compiler now derivesnamefrom the exercise_ref / modality / category token for Exercise, Cardio, Nutrition, Meditation, Recovery, and Habit activities, matching the TShumanise()helper exactly. Acronyms (HIIT, AMRAP, EMOM, RPE, RIR, 1RM) are uppercased; all other words are title-cased. Explicitnameset in the DSL is preserved as-is (TS parity).
[1.6.0] — 2026-05-05
Added
Contraindication.severity + require_clearance(schema v1.6.0) — extends the contraindication DSL tocontraindication <name> [severity <low|moderate|high>] [action <action>]where action now includesrequire_clearance. The old arrow form (contraindication <name> -> <action>) is preserved for back-compat. Compiler emitsseverityonly when present.Reps.amrap(schema v1.6.0) — DSL:<exercise> NxAMRAP(compact) orNx amrap(space-separated, case-insensitive). Compiler emitsprescription.reps: { amrap: true }. Sets count is preserved from N.ExercisePrescription.to_failure(schema v1.6.0) — optional modifierto_failurein the exercise modifier chain. Compiler emitsprescription.to_failure: truewhen present; field omitted otherwise.Weight.metricqualifier (schema v1.6.0) — optionalmetric <1rm|e1rm|training_max|daily_max>after aweight N% rmspec. Compiler emitsweight.metric: "<canonical>"(e.g."1RM","e1RM","training_max","daily_max"). Omitted when not specified (back-compat).RecoveryExerciseextensions (schema v1.6.0) — optional modifiers on recovery exercise lines:modality <enum>(7 values:static_stretch | dynamic_stretch | pnf | smr_foam_roll | smr_ball | breathwork | mobility_drill),intensity <1-10>→ emitsintensity_rpe,body <token>→ emitsbody_part. Optional indentedpnf <Ns> contract <Ns> relax <int> contractionscontinuation line emits{ contraction_seconds, relax_seconds, contractions }. Recovery exercises now compiled underprescription.exercisesto match TS schema.- Checkpoint typed
MeasurementSpec(schema v1.6.0) —measure:lists now accept bare metric tokens (emitting{ metric: "<value>" }) and<metric> questionnaire <enum> [note "text"](emitting full typed spec). Quoted strings preserved as plain strings (back-compat). Added support for TS-style inlineCHECKPOINT "Name":blocks withat N weekstrigger form. - Cardio
intensity.target.min_bpm/max_bpm(schema v1.6.0) —intensity bpm N..Mnow compiles toprescription.intensity: { type: "bpm", target: { min_bpm: N, max_bpm: M } }. - Emitted
versionbumped to"1.6.0"— compiler now emits"version": "1.6.0"in all compiled plans.
[1.5.0] — 2026-05-04
Added
Phase.typeenum (schema v1.5.0) — DSL:PHASE "Name" <type> (N weeks):where<type>is one ofaccumulation | intensification | realization | deload | base | build | peak | recovery | transition. Compiler emitsphase.type: "<value>". When omitted, notypekey is emitted on the phase object.Week.is_deload(schema v1.5.0) — DSL:WEEK N deload(optional token immediately after the week number, before the optional name string). Compiler emitsweek.is_deload: true. When absent, the field is omitted (not emitted asfalse).SubPlanActivity(schema v1.5.0) — new activity variantsubplan <plan-id>(optionally followed by a quoted name string) inside any block. Compiler emits{ type: "sub_plan", id: "sub_plan_N", sub_plan_ref: "<plan-id>", name?: "<optional>" }.
[1.4.0] — 2026-05-04
Added
- Per-kg macros + per-kg cals + TDEE multiplier (schema v1.4.0) — DSL accepts unit suffixes
g_per_kgonprotein,carbs,fatlines andkcal_per_kg/multiplier_of_tdeeoncalorieslines. Compiler emits the unit verbatim intoMacroRange.unit/Calories.unit. Default units remain"g"and"kcal"when no suffix is given. Weight.percentage_bodyweight(schema v1.4.0) — DSL:weight N% bworweight N% bodyweight. Compiler emitsWeightwithtype: "percentage_bodyweight",value: N,unit: "%". Existingweight N kg(absolute) andweight N% rm(percentage_1rm) forms are unchanged.
[1.3.0] — 2026-05-04
Fixed
- Compiler now emits the canonical
$schemaURLhttps://wpl.dev/schemas/wpl/v1.schema.json(previously emittedhttps://gymbile.com/schemas/wpl/v1).
Added
- MuscleGroup + MovementPattern enums — DSL:
<exercise> NxR muscles primary <m1>, <m2> secondary <m3> pattern <p>. Compiler emitsprimary_muscles,secondary_muscles, andmovement_patternonExerciseActivity. Supports all 22MuscleGroupvalues and all 13MovementPatternvalues from schema v1.3.0. - Cardio
zone_model— DSL:zone N model <zone_model>qualifier inside acardioblock. Compiler emitsintensity.zone_model(7 values:hr_3_zone_seiler,hr_5_zone,hr_7_zone,power_coggan_7_zone,pace_critical_speed,rpe_borg_10,rpe_borg_20). New intensity typeintensity power Nemitsintensity.type: "power". - Plan-level
ATHLETE_THRESHOLDSblock — top-level DSL section (parallel toPHASES). Acceptshr_max N bpm,lthr N bpm,resting_hr N bpm,ftp N watts,vo2max N,critical_pace N,body_weight N kg,one_rm <exercise> N kg. Compiler emitsplan.athlete_thresholdswith field-name suffixes matching schema v1.3.0 (hr_max_bpm,lthr_bpm,resting_hr_bpm,ftp_watts,vo2max_ml_kg_min,critical_pace_seconds_per_km,body_weight_kg,one_rm: [{ exercise_ref, value, unit }]).
[1.0.0] — 2026-05-04
Added
- Initial extract from
gymbile_backend. Compiler emits WPL schema 1.0.0. WplAi.parse/1— WPL-AI DSL text →WplAi.AST.Documentstruct.WplAi.compile/1— AST → WPL JSON map (string keys,"version": "1.0.0").WplAi.to_wpl/1— parse + compile in one step.WplAi.decompile/1— WPL JSON → WPL-AI text (round-trip).WplAi.tokenize/1— exposes the lexer for tooling / syntax highlighting.WplAi.validate/1— fast validity check without full compilation.WplAi.ExerciseMatcher— Jaro-Winkler fuzzy matching for exercise references.WplAi.Errors— structured error types (LexerError,ParseError,CompileError) with LLM-optimised formatting helpers.- Significant-indentation lexer (Python-style
INDENT/DEDENT). - Recursive-descent parser covering: header, goals, requirements, personalization,
phases/weeks/days/blocks, exercise / cardio / nutrition / meditation / recovery /
habit / simple activities, progress checkpoints, top-level
HABITSsection.
Notes
Phase 2 will update the emitted schema version and bring the compiler to parity
with @gymbile/wpl-ai v1.6.0. Every plan valid under schema 1.0.0 will continue
to compile correctly.